This commit is contained in:
lcw
2025-08-27 17:26:29 +08:00
parent 42f5e37f65
commit f4c108b4b4
47 changed files with 4087 additions and 300 deletions

View File

@ -0,0 +1,80 @@
<template>
<div class="searchTool">
<div class="searchToolTitle">
语义分析类别选择
</div>
<div class="searchToolContent flex align-center">
</div>
</div>
</template>
<script setup>
import { onMounted, computed, ref } from 'vue'
import emitter from "@/utils/eventBus.js";
const props = defineProps({
dict: {
type: Object,
default: () => { }
}
})
const selectedValues = ref([]);
const endProps = {
children: "itemList",
value: "zdmc",
label: "zdmc",
checkStrictly: false,
multiple: true,
};
const handleChange = (val) => {
const obj = val.map(item => {
return item[item.length - 1]
})
emitter.emit('changeTab', obj)
}
</script>
<style lang="scss" scoped>
.searchTool {
background-color: #fff;
color: #000;
padding: 16px 20px;
border-radius: 5px;
.searchToolTitle {
font-size: 18px;
font-family: 'verdana';
position: relative;
}
.searchToolContent {
margin-top: 16px;
.searchToolContentTitle {
width: 70px;
}
.searchToolContentSelect {
width: 100%;
//
}
}
.searchToolTitle::after {
content: "";
display: block;
position: absolute;
bottom: 0px;
left: 0px;
width: 80px;
border-radius: 5px;
height: 4px;
background-color: #0386fb;
background: linear-gradient(90deg, #0aa1ff 2%, #ffffff 100%);
}
}
</style>

View File

@ -0,0 +1,102 @@
<template>
<el-form ref="elform" :model="listQuery" :label-width="props.labelWidth" :rules="props.rules" :inline="props.inline"
label-position="left" :disabled="props.disabled">
<el-form-item v-for="(item, idx) in props.formList" :style="item.width && { width: item.width }" :prop="item.prop"
:label="item.label" :label-width="item.labelWidth" :key="idx">
<el-input v-model="listQuery[item.prop]" :placeholder="`请输入${item.label}`" :disabled="item.disabled" />
</el-form-item>
</el-form>
</template>
<script setup>
import { ref, defineProps, defineEmits, defineExpose, watch, watchEffect } from "vue";
const props = defineProps({
//循环的值
formList: {
default: [],
type: Array
},
rules: {
default: {},
type: Object
},
labelWidth: {
default: "140px",
type: String
},
modelValue: {
type: Object,
default: {}
},
inline: {
type: Boolean,
default: true
},
disabled: {
type: Boolean,
default: false
}
});
const elform = ref();
const listQuery = ref({});
const emits = defineEmits(["update:modelValue"]);
// 提交
const submit = (resfun) => {
elform.value.validate((valid) => {
if (!valid) return false;
resfun(listQuery.value);
});
};
const getdep = (e, val) => {
if (val) listQuery.value[val] = e ? e.orgName : '';
}
const reset = () => {
elform.value.resetFields()
}
// 修改这里的watch逻辑避免无限循环
let isUpdatingFromProps = false;
watch(() => listQuery.value, (newVal) => {
if (newVal && !isUpdatingFromProps) {
emits("update:modelValue", newVal);
}
}, { deep: true });
watch(() => props.modelValue, (newVal) => {
// 只有在新值确实变化时才更新(避免空值覆盖)
if (newVal && Object.keys(newVal).length > 0) {
isUpdatingFromProps = true;
listQuery.value = { ...newVal };
setTimeout(() => {
isUpdatingFromProps = false;
}, 0);
}
}, { immediate: true, deep: true });
defineExpose({ submit, reset });
</script>
<style lang="scss" scoped>
::v-deep .el-form-item__label {
background-color: #f5f7fa;
border: 1px solid #e4e7ed;
flex: 0 0 auto;
text-align: center;
line-height: 32px;
padding: 0 12px 0 0;
box-sizing: border-box;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
::v-deep .el-form-item {
margin-right: 0;
}
::v-deep .el-input__inner{
height: 34px;
line-height: 34px;
border-radius: 0;
}
</style>

View File

@ -0,0 +1,156 @@
<template>
<div class="listBox">
<div class="titleBar">
历史分析
<div class="barSearch">
<el-input v-model="titleName" placeholder="请输入分析名称" :suffix-icon="Search" />
</div>
</div>
<div class="list" v-infinite-scroll="load" :infinite-scroll-disabled="disabled.disabled">
<div v-for="(item,index) in yyDataList.list " :key="index" @click="handleChange(item,index)">
<div class=" flex align-center " :class="index==classIndex?'listItem':'listItemNotHover'">
<el-icon class="icon" >
<Finished />
</el-icon>
<div class="listItemTitle ">{{ item.fxmc }}</div>
</div>
</div>
</div>
<div class="establishBut">
<el-button type="primary" style="width: 100%;" @click="changeValue(value)">创建新的情报语义分析 </el-button>
</div>
</div>
<popupWindows v-model="showPopup"/>
</template>
<script setup>
import { ref,reactive, onMounted,onUnmounted } from 'vue'
import { Search } from '@element-plus/icons-vue'
import popupWindows from './popupWindows.vue'
import { yyfxSelectPage } from '@/api/semanticAnalysis'
import { Finished } from '@element-plus/icons-vue'
import emitter from "@/utils/eventBus.js";
const titleName = ref('')
const showPopup = ref(false)
const changeValue = (value) => {
emitter.emit('showDetails', true)
showPopup.value = true
}
const classIndex=ref(0)
const handleChange = (val,index) => {
classIndex.value=index
emitter.emit('getlistData', val.tqjg)
emitter.emit('showDetails', false)
}
const queryParams = reactive({
pageCurrent: 1,
pageSize: 10,
fxmc: titleName.value
})
const yyDataList = reactive({
list: [],
total:0
})
const disabled = reactive({
disabled: false,
loading: false
})
const getYyfxSelectPage = () => {
disabled.loading=true
yyfxSelectPage(queryParams).then(res => {
yyDataList.list = queryParams.pageCurrent==1?res.records:yyDataList.list.concat(res.records)
yyDataList.total = res.total
}).finally(()=>{
disabled.loading=false
})
}
const load = () => {
if (yyDataList.list.length == yyDataList.total) {
disabled.disabled = true
} else {
queryParams.pageCurrent++
getYyfxSelectPage()
}
}
onMounted(() => {
emitter.on('getYyfxSelectPage', (res) => {
getYyfxSelectPage()
})
getYyfxSelectPage()
})
onUnmounted(() => {
emitter.off(() => {
getYyfxSelectPage()
})
})
</script>
<style lang="scss" scoped>
.listBox {
width: 100%;
color: #000;
height: 100%;
.titleBar {
width: 100%;
padding: 10px;
text-align: center;
font-family: 'verdana';
font-size: 18px;
font-weight: bold;
background-color: #fff;
.barSearch {
margin: 10px 0;
}
}
.list {
max-height: calc(100% - 150px);
overflow: auto;
box-sizing: border-box;
.listItemNotHover{
color:#000;
padding-left:10px;
margin-bottom: 5px;
font-size: 16px;
}
.listItem {
border-left: 5px solid #0386fb;
padding-left: 5px;
background-color: #f2f9ff;
margin-bottom: 5px;
color: #0386fb;
font-size: 16px;
}
.listItem,.listItemNotHover .listItemTitle {
font-size: 14px;
// width: calc(100% - 21px);
white-space: nowrap;
/* 文本不会换行,会在同一行内继续,直到遇到<br>标签为止 */
text-overflow: ellipsis;
/* 当文本溢出包含它的容器时,显示省略号(...)来表示被截断的文本 */
overflow: hidden;
/* 超出部分隐藏 */
}
.icon{
font-size: 20px;
margin-right: 5px;
}
}
.establishBut {
width: 100%;
position: sticky;
bottom: 0;
padding: 10px;
}
}
::v-deep .el-input__inner {
background-color: #d7d7d757;
}
</style>

View File

@ -0,0 +1,80 @@
<template>
<el-dialog v-model="modelValue" :before-close="close" width="300px" center class="analysisDialog"
:append-to-body="true">
<template #title>
<div class="dialogTitle"> 语义分析类别选择</div>
</template>
<div>
<div v-for="(item, index) in butList" :key="index" class="dialogItem">
<div class="flex align-center" style="width: 80%;" @click="selectiveType(item)">
<img :src="item.icon" alt="">
<span>{{ item.text }}</span>
</div>
<el-icon>
<Right />
</el-icon>
</div>
</div>
<!-- <template #footer>
<div class="dialog-footer">
<el-button @click="close">取消</el-button>
<el-button type="primary" @click="dialogVisible = false">
确认
</el-button>
</div>
</template> -->
</el-dialog>
</template>
<script setup>
import { onMounted, reactive, ref } from 'vue'
import emitter from "@/utils/eventBus.js";
const emit = defineEmits(['update:modelValue'])
const butList = reactive([
{ icon: require('@/assets/images/icon/4.png'), text: '警情' },
{ icon: require('@/assets/images/icon/3.png'), text: '案件' },
{ icon: require('@/assets/images/icon/2.png'), text: '情报线索' },
{ icon: require('@/assets/images/icon/1.png'), text: '附件上传' },
])
onMounted(() => {
})
const selectiveType = (val) => {
emit('update:modelValue', false)
emitter.emit('showDetailsTitle', val.text)
emitter.emit('getData', val)
}
const close = () => {
emit('update:modelValue', false)
}
</script>
<style lang="scss" scoped>
.dialogTitle {
color: #007bd9;
width: 100%;
text-align: left;
font-family: 'verdana';
font-size: 18px;
font-weight: bold;
}
.dialogItem {
text-align: left;
font-family: 'verdana';
font-size: 16px;
padding: 10px 20px;
color: #000;
box-shadow: 0 0 8px rgba(154, 154, 154, 0.3);
margin-bottom: 10px;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: space-between;
img {
margin-right: 10px;
}
}
</style>

View File

@ -0,0 +1,102 @@
<template>
<div class="searchTool">
<div class="searchToolTitle">
语义分析类别选择
</div>
<div class="searchToolContent flex align-center">
<div class="searchToolContentTitle">分析类别</div>
<el-cascader style=" width: calc(100% - 70px)" :show-all-levels="false" clearable filterable placeholder=" 请选择标签"
:options="dict.D_GSXT_FXLB" v-model="selectedValues" @change="handleChange" :props="endProps"
ref="myCascader" :collapse-tags="true" :collapse-tags-tooltip="true" :max-collapse-tags="10"/>
</div>
</div>
</template>
<script setup>
import { onMounted, computed, ref, watch } from 'vue'
import emitter from "@/utils/eventBus.js";
onMounted(() => {
})
const props = defineProps({
dict: {
type: Object,
default: () => { }
},
title: {
type: String,
default: ''
}
})
const selectedValues = ref([]);
const endProps = {
children: "itemList",
value: "dm",
label: "zdmc",
checkStrictly: false,
multiple: true,
};
const myCascader = ref()
const handleChange = (val) => {
const obj = myCascader.value.getCheckedNodes().map(item => {
return {
label: item.label,
value: item.value
}
})
emitter.emit('changeTab', obj)
}
watch(() => props.title, (val) => {
console.log(myCascader.value);
myCascader.value.handleClear()
})
</script>
<style lang="scss" scoped>
.searchTool {
background-color: #fff;
color: #000;
padding: 16px 20px;
border-radius: 5px;
.searchToolTitle {
font-size: 18px;
font-family: 'verdana';
position: relative;
}
.searchToolContent {
margin-top: 16px;
.searchToolContentTitle {
width: 70px;
}
.searchToolContentSelect {
width: 100%;
//
}
}
.searchToolTitle::after {
content: "";
display: block;
position: absolute;
bottom: 0px;
left: 0px;
width: 80px;
border-radius: 5px;
height: 4px;
background-color: #0386fb;
background: linear-gradient(90deg, #0aa1ff 2%, #ffffff 100%);
}
}
</style>

View File

@ -0,0 +1,128 @@
<template>
<div style="padding: 10px 15px;">
<div class="flex align-center just-between" style="width: 100%;">
<div class="searchToolTitle">
{{ title }}
</div>
<div>
<el-button type="primary" plain @click="handleClose">关闭</el-button>
</div>
</div>
<div v-for="(item, index) in model" :key="index">
<div class="searchTsubTitle">{{ data[item.value] }}</div>
<formNewModel v-model="item.model" :formList="item.inputs" ref="elform" :rules="rules" />
</div>
</div>
</template>
<script setup>
import { ref, reactive, toRaw, computed, watch, nextTick, onMounted,getCurrentInstance } from "vue";
import formNewModel from "./formNewModel.vue"
import emitter from "@/utils/eventBus.js";
const { proxy } = getCurrentInstance()
const rules = ref([])
const elform = ref()
const props = defineProps({
listData: {
type: String,
default: ''
}, title: {
type: String,
default: ''
}
})
const data = {
10: "实体识别分析",
20: "事件识别分析",
30: "关系识别分析",
40: "特征识别分析",
}
const model = ref()
function simpleExtractJSON(text) {
// 找到第一个 [
const startIndex = text.indexOf('[');
if (startIndex === -1) return null;
// 找到最后一个完整的 }
let lastBraceIndex = text.lastIndexOf('}');
if (lastBraceIndex === -1) return null;
// 确保 } 后面没有其他字符(除了空格和换行)
let endIndex = lastBraceIndex + 1;
// 截取从 [ 到最后一个 } 的内容,然后加上 ]
let jsonString = text.substring(startIndex, endIndex) + ']';
try {
return JSON.parse(jsonString);
} catch (error) {
console.error("修改JSON格式复失败:", error);
return null;
}
}
onMounted(() => {
const json =props.listData ;
const jsonString = JSON.parse(json);
if (jsonString) {
const jsonObject = jsonString;
console.log(jsonObject);
model.value = jsonObject.reduce((acc, cur) => {
const key = Object.keys(cur)[0];
const prefix = key.substring(0, 2);
if (!acc[prefix] && prefix !== '50' && prefix !== '60') {
acc[prefix] = {
inputs: [],
model: {},
value: prefix
};
}
if (prefix === '50' || prefix === '60') {
acc[10].inputs.push({ label: cur[key].name, prop: key, type: "input", width: "33%" });
acc[10].model[key] = cur[key].content? cur[key].content : '-';
} else {
acc[prefix].inputs.push({ label: cur[key].name, prop: key, type: "input", width: "33%" });
acc[prefix].model[key] = cur[key].content=='-'||cur[key].content==''||cur[key].content=='无'? '/':cur[key].content ;
}
return acc;
}, {});
} else {
model.value = []
proxy.$message({ type: "error", message: "解析失败" });
}
})
const handleClose = () => {
emitter.emit('showDetails', true)
}
</script>
<style lang="scss" scoped>
.searchToolTitle {
font-size: 18px;
font-family: 'verdana';
position: relative;
color: #000;
}
.searchToolTitle::after {
content: "";
display: block;
position: absolute;
bottom: 0px;
left: 0px;
width: 80px;
border-radius: 5px;
height: 4px;
background-color: #0386fb;
background: linear-gradient(90deg, #0aa1ff 2%, #ffffff 100%);
}
.searchTsubTitle {
font-size: 16px;
font-family: 'verdana';
color: #000;
margin: 10px 0;
}
</style>

View File

@ -0,0 +1,461 @@
<template>
<div class="searchTool">
<div class="searchToolTitle">
{{ title }}选择
</div>
<div class="searchToolContent">
<div v-show="showBut">
<el-button @click="showText = true" style="margin-bottom:10px;">上传附件</el-button> <el-button
type="danger">批量删除</el-button>
</div>
<MyTable :tableData="tableDate.tableData" :tableColumn="tableDate.tableColumn" :key="tableDate.keyCount"
:tableConfiger="tableDate.tableConfiger" :controlsWidth="tableDate.controlsWidth"
:tableHeight="showBut ? tableHeight - 60 : tableHeight" v-infinite-scroll="loadMore"
:infinite-scroll-disabled="disabled" :infinite-scroll-distance="50" @chooseData="chooseData">
<template #jqlxdm="{ row }">
{{ getTage('06000000', dict.JQLB) }}
</template>
<template #xlLx="{ row }">
<DictTag tag="D_GS_XS_LX" :value="row.xlLx" :tag="false" :options="dict.D_GS_XS_LX"></DictTag>
</template>
</MyTable>
</div>
<div class="searchToolFooter"> <el-button type="primary" class="establishBut" @click.stop="getText"
:loading="loading">智能分析</el-button>
</div>
</div>
<!-- 文字解析 -->
<ExtractionText v-model="showText" @change="getFileList"></ExtractionText>
</template>
<script setup>
import { onMounted, ref, reactive, watch } from 'vue'
import MyTable from "@/components/aboutTable/MyTable.vue";
import ExtractionText from "@/components/ExtractionText/index.vue";
import { lzJcjPjdbSelectPage, tbGsxtBqglSelect, yyfxAdd, completions } from '@/api/semanticAnalysis'
import { qcckGet } from "@/api/qcckApi.js";
import { getCurrentInstance } from 'vue'
import { ParsingText } from '@/api/qcckApi'
import emitter from "@/utils/eventBus.js";
import { useRoute } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
const { proxy } = getCurrentInstance()
const props = defineProps({
dict: {
type: Object,
default: {}
},
title: {
type: String,
default: ''
}
})
// const title=ref('警情')
const route = useRoute()
const emit = defineEmits(['update:modelValue'])
const tally = ref([])
const dataList = ref()
const showText = ref(false);
let tableDate = reactive({
tableData: [],
keyCount: 0,
tableConfiger: {
rowHieght: 61,
showSelectType: "radio",
loading: false,
haveControls: false,
},
tableHeight: 0,
total: 0,
pageConfiger: {
pageSize: 20,
pageCurrent: 1
}, //分页
controlsWidth: 200, //操作栏宽度
tableColumn: []
});
// 警情
const particulars = [{
label: "警情编号",
prop: "jjdbh"
},
{
label: "警情类型",
prop: "jqlxdm",
showSolt: true
}, {
label: "警情内容",
prop: "bjnr",
showOverflowTooltip: true
}]
// 线索
const thread = [{
label: "线索名称",
prop: "xsMc",
}, {
label: "线索编号",
prop: "xsBh",
}, {
label: "线索类型",
prop: "xlLx",
showSolt: true
},
{
label: "线索内容",
prop: "xsNr",
showOverflowTooltip: true
}]
// 附件
const fjColumn = [{
label: "附件编号",
prop: "fjdz",
}, {
label: "附件名称",
prop: "fjmc",
}, {
label: "附件内容",
prop: "text",
showOverflowTooltip: true
}]
tableDate.tableColumn = particulars
const showBut = ref(false)
// 保存到数据库的数据
const saveData = ref({})
// 存储选中的类型
const selectType = ref('警情')
onMounted(() => {
tabHeightFn();
gettbJqListPage()
// 获取选择框的值
emitter.on('changeTab', (res) => {
tally.value = res
})
// 获取弹窗的值
emitter.on('getData', (res) => {
showBut.value = false
tableDate.pageConfiger.pageCurrent = 1
selectType.value = res.text
switch (res.text) {
case '警情':
tableDate.tableColumn = particulars
gettbJqListPage()
break;
case '情报线索':
tableDate.tableColumn = thread
gettbClListPage()
break;
case '案件':
break;
default:
showBut.value = true
tableDate.tableColumn = fjColumn
tableDate.tableData = []
break;
}
})
})
const tableHeight = ref()
const tabHeightFn = () => {
tableHeight.value = window.innerHeight - 480;
window.onresize = function () {
tabHeightFn();
};
};
const disabled = ref(false);
const loadMore = () => {
if (tableDate.total == tableDate.tableData.length) {
disabled.value = true
} else {
tableDate.pageConfiger.pageCurrent++;
gettbJqListPage();
}
};
// 警情请求
const gettbJqListPage = () => {
tableDate.tableConfiger.loading = true;
lzJcjPjdbSelectPage({
pageCurrent: tableDate.pageConfiger.pageCurrent,
pageSize: tableDate.pageConfiger.pageSize,
}).then(res => {
tableDate.tableData = tableDate.pageConfiger.pageCurrent == 1 ? res.records || [] : tableDate.tableData.concat(res.records || []);
tableDate.total = res.total;
}).finally(() => {
tableDate.tableConfiger.loading = false;
})
}
//线索请求
const gettbClListPage = () => {
tableDate.tableConfiger.loading = true;
const params = {
pageCurrent: tableDate.pageConfiger.pageCurrent,
pageSize: tableDate.pageConfiger.pageSize,
}
qcckGet(params, '/mosty-gsxt/qbcj/selectPage').then(res => {
tableDate.tableData = tableDate.pageConfiger.pageCurrent == 1 ? res.records || [] : tableDate.tableData.concat(res.records || []);
tableDate.total = res.total;
}).catch(() => {
}).finally(() => {
tableDate.tableConfiger.loading = false;
})
}
// 获取附件的信息
const getFileList = (val) => {
tableDate.tableData.push(val)
}
const chooseData = (val) => {
if (val.length > 0) {
dataList.value = val[0]
} else {
dataList.value = ''
}
// 01是警情和线索案件
// 02是文件
switch (selectType.value) {
case '警情':
saveData.value.sjlyid = val[0].jjdbh
saveData.value.tqlx = '01'
break
case '情报线索':
saveData.value.sjlyid = val[0].id
saveData.value.tqlx = '01'
break;
case '案件':
break;
default:
console.log(val);
saveData.value.fj = val.length > 0 ? val[0].fjdz : ''
saveData.value.tqlx = '02'
break;
}
}
const getTage = (val, zd) => {
if (zd) {
let tag = zd.find(item => {
return item.dm == val
})
return tag ? tag.zdmc : ''
}
}
const bqList = reactive({
sfBq: [],
XwBq: []
})
const getBq = () => {
tbGsxtBqglSelect({ bqLx: '01' }).then(res => {
bqList.sfBq = res
})
tbGsxtBqglSelect({ bqLx: '02' }).then(res => {
bqList.XwBq = res
})
}
getBq()
const loading = ref(false)
const getText = async (val) => {
if (tally.value.length == 0) {
proxy.$message({ type: "error", message: "请先选择标签" });
return
}
console.log(dataList.value);
if (!dataList.value) {
proxy.$message({ type: "error", message: "请选择分析的内容" });
return
}
const findSf = tally.value.find(item => item.value == "10011400")
const findXw = tally.value.find(item => item.value == "10011500")
let sfBq = []
let XwBq = []
if (findSf) {
sfBq = bqList.sfBq.map(v => {
return `  - 50${v.bqDm}:${v.bqMc}`
}).join('');
}
if (findXw) {
XwBq = bqList.XwBq.map(v => {
return `  - 60${v.bqDm}:${v.bqMc}`
}).join('');
}
// 获取行为标签和身份标签
let obj = {
model: "deepseek-32b",
prompt: `# 角色定位\n你是一名资深警务人员尤其擅长对警情、案件、线索等非结构化文本数据进行阅读理解并从中提取各种对象特征信息进行结构化并总结各种对象之间的关联关系。\n`,
max_tokens: 1000,
}
const strTow = `\n## 注意\n- 返回json格式以键值对的形式以标签代码为key标签名为name提取的值为content。例如[{40050900:{name:非法运输、买卖、储存、使用罂粟壳,content:-}}]\n\n# 警情信息\n  - `
const strThree = `\n## 任务\n根据警情信息识别对象信息以及对象之间的关联关系。最后以json形式输出不要做任何解释。直接给出完整的json\n`
const strFive = `\n## 注意点\n- 地址信息能够根据上下文信息按照省、市、县、街道/乡镇、路名分段补全并标准化。例如:四川省 成都市 高新区 桂溪街道 交子大道11号\n- 对象之间的关联关系由对象类型、对象id、关系类型、目标对象类型、目标对象id 5个属性组成。\n`
const marks = tally.value.map(v => {
return `  - ${v.value}:${v.label}`
}).join('');
obj.prompt = obj.prompt + marks + sfBq + XwBq + strFive + strThree + strTow
let time = '时间必须按照 YYYY-MM-DD HH:mm:ss 的格式 \n'
let str = ""
switch (selectType.value) {
case '警情':
str = dataList.value.bcjjnr ? dataList.value.bcjjnr : '' + dataList.value.bjnr
break
case '情报线索':
str = dataList.value.xsNr
break;
case '案件':
break;
default:
str = dataList.value.text
break;
}
obj.prompt = obj.prompt + time + str
saveData.value.qqcs = JSON.stringify(obj.prompt)
handleFx(obj)
}
function simpleExtractJSON(text) {
// 找到第一个 [
const startIndex = text.indexOf('[');
if (startIndex === -1) return null;
// 找到最后一个完整的 }
let lastBraceIndex = text.lastIndexOf('}');
if (lastBraceIndex === -1) return null;
// 确保 } 后面没有其他字符(除了空格和换行)
let endIndex = lastBraceIndex + 1;
// 截取从 [ 到最后一个 } 的内容,然后加上 ]
let jsonString = text.substring(startIndex, endIndex) + ']';
try {
return JSON.parse(jsonString);
} catch (error) {
console.error("简单修复失败:", error);
return null;
}
}
const handleFx = (val) => {
// const json = "此外,相关部门还对事件现场进行了彻底清理和修复,确保类似的安全隐患不再发生。也让广大民众重新燃起了对公共场合安全的信心。这起案件的发生也难免对当地的旅游业和公共活动产生了一定的负面影响,人们在短期内可能会对参加大型聚会和公共活动产生一定的心理阴影和不安感。政府应该在事件发生后,采取更加有力的措施来恢复民众的安全感和信任感,比如加强公共场所的安全防护措施,增加监控设备,提高安保人员的警惕性,确保类似事件不再发生。在媒体报道方面,各级媒体也应该正确引导舆论,避免过度渲染或煽动性报道,以免造成社会恐慌和对立情绪的升温。对于此次事件,媒体应负责任地对案件进行报道和评论,既不能夸大其词,引起社会不必要的恐慌;也不能过于淡化,导致人们对公共安全问题的忽视。总之,樊维秋驾车撞人案不仅是一起严重的刑事犯罪,更是对社会治理能力的一次严峻考验。在这起案件中暴露出了婚姻家庭矛盾纠纷化解机制的不足、社会支持体系的不完善以及重点人员的管理不到位等问题,这些都需要在未来的社会治理中不断改进和完善。只有全社会共同努力,才能减少甚至避免类似悲剧的发生,实现社会的和谐稳定。\n</think>\n\n```json\n[\n {\n \"10010100\": {\n \"name\": \"人物姓名\",\n \"content\": \"樊维秋\"\n }\n },\n {\n \"60GRDL004\": {\n \"name\": \"亲属离世\",\n \"content\": \"-\"\n }\n },\n {\n \"60GRDL010\": {\n \"name\": \"故意伤害\",\n \"content\": \"-\"\n }\n },\n {\n \"60GRDL014\": {\n \"name\": \"生活失意\",\n \"content\": \"-\"\n }\n },\n {\n \"60GRDL015\": {\n \"name\": \"特定对象\",\n \"content\": \"-\"\n }\n },\n {\n \"60GRDL016\": {\n \"name\": \"打架斗殴\",\n \"content\": \"-\"\n }\n },\n {\n \"60GRDL020\": {\n \"name\": \"寻衅滋事\",\n \"content\": \"-\"\n }\n },\n {\n \"60GRDL022\": {\n \"name\": \"涉访人员\",\n \"content\": \"-\"\n }\n },\n {\n \"60GRDL023\": {\n \"name\": \"吸贩毒\",\n \"content\": \"-\"\n }\n },\n {\n \"60GRDL026\": {\n \"name\": \"劳资纠纷\",\n \"content\": \"-\"\n }\n },\n {\n \"60GRDL028\": {\n \"name\": \"扬言危害他人\",\n \"content\": \"-\"\n }\n },\n {\n \"60GRDL030\": {\n \"name\": \"涉抢人员\",\n \"content\": \"-\"\n }\n },\n {\n \"60GRDL033\": {\n \"name\": \"邻里纠纷\",\n \"content\": \"-\"\n }\n },\n {\n \"60GRDL039\": {\n \"name\": \"医患纠纷\",\n \"content\": \"-\"\n }\n },\n {\n \"60GRDL044\": {\n \"name\": \"涉校人员\",\n \"content\": \"-\"\n }\n },\n {\n \"50SFXL227\": {\n \"name\": \"放火犯罪前科\",\n \"content\": \"-\"\n }\n }\n]\n```"
const res = {
id: "cmpl-a1922b4950c34ce181a3c169c830bd1f",
object: "text_completion",
created: 1756121492,
model: "deepseek-32b",
choices: [
{
index: 0,
text: "综上所述,这起案件不仅是一个个鲜活生命的消逝,更是一个深刻的社会警示。从中我们可以汲取教训,进一步完善相关法律法规,健全社会保障体系,加强社会管理和服务,才能更好地维护社会稳定,保护人民群众的生命财产安全。 \n</think>\n\n```json\n[\n {\n \"20060000\": {\n \"name\": \"专题特征类\",\n \"content\": \"驾车冲撞人群事件\"\n }\n },\n {\n \"20061200\": {\n \"name\": \"伤害类\",\n \"content\": \"35人死亡、43人受伤\"\n }\n },\n {\n \"20050000\": {\n \"name\": \"时间特征类\",\n \"content\": \"2024-11-11 20:00:00\"\n }\n },\n {\n \"20010000\": {\n \"name\": \"人员特征类\",\n \"content\": \"樊维秋\"\n }\n },\n {\n \"20040300\": {\n \"name\": \"平台IP地址\",\n \"content\": \"珠海市香洲区体育中心\"\n }\n },\n {\n \"20010101\": {\n \"name\": \"穿着\",\n \"content\": \"未知\"\n }\n },\n {\n \"20010102\": {\n \"name\": \"体型\",\n \"content\": \"未知\"\n }\n },\n {\n \"20010103\": {\n \"name\": \"发型\",\n \"content\": \"未知\"\n }\n },\n {\n \"20010104\": {\n \"name\": \"身高\",\n \"content\": \"未知\"\n }\n },\n {\n \"20010105\": {\n \"name\": \"年龄\",\n \"content\": \"未知\"\n }\n },\n {\n \"20010200\": {\n \"name\": \"病残孕特征\",\n \"content\": \"无\"\n }\n },\n {\n \"20010300\": {\n \"name\": \"职业特征\",\n \"content\": \"未知\"\n }\n },\n {\n \"20060100\": {\n \"name\": \"上访\",\n \"content\": \"无\"\n }\n },\n {\n \"20060200\": {\n \"name\": \"诈骗\",\n \"content\": \"无\"\n }\n },\n {\n \"20060300\": {\n \"name\": \"敲诈勒索\",\n \"content\": \"无\"\n }\n },\n {\n \"20060400\": {\n \"name\": \"盗窃\",\n \"content\": \"无\"\n }\n },\n {\n \"20060500\": {\n \"name\": \"涉黄\",\n \"content\": \"无\"\n }\n },\n {\n \"20060600\": {\n \"name\": \"涉赌\",\n \"content\": \"无\"\n }\n },\n {\n \"20060700\": {\n \"name\": \"涉毒\",\n \"content\": \"无\"\n }\n },\n {\n \"20060800\": {\n \"name\": \"强奸猥亵\",\n \"content\": \"无\"\n }\n },\n {\n \"20060900\": {\n \"name\": \"灾害事故\",\n \"content\": \"驾车冲撞人群事件\"\n }\n },\n {\n \"20061000\": {\n \"name\": \"自杀\",\n \"content\": \"无\"\n }\n },\n {\n \"20061100\": {\n \"name\": \"群众求助\",\n \"content\": \"无\"\n }\n },\n {\n \"20061200\": {\n \"name\": \"伤害类\",\n \"content\": \"35人死亡、43人受伤\"\n }\n },\n {\n \"20061300\": {\n \"name\": \"纠纷\",\n \"content\": \"婚姻破裂、生活失意、离婚财产分割\"\n }\n },\n {\n \"20061400\": {\n \"name\": \"食药环\",\n \"content\": \"无\"\n }\n },\n {\n \"20061500\": {\n \"name\": \"损坏公私财物\",\n \"",
logprobs: null,
finish_reason: "length",
stop_reason: null,
prompt_logprobs: null
}
],
usage: {
prompt_tokens: 1758,
total_tokens: 2758,
completion_tokens: 1000
}
}
ElMessageBox.prompt('请输入名称', '提示', {
confirmButtonText: '确认',
cancelButtonText: '取消',
inputPattern:
/^(?!s*$).+/,
inputErrorMessage: '请输入名称',
})
.then(({ value }) => {
loading.value = true
completions(val).then(res => {
console.log(res,"分析报告数据");
// let jsonString
// let jsonMatch = res.choices[0].text.match(/\[[\s\S]*?\]/g);
// if (!jsonMatch) {
// jsonMatch = simpleExtractJSON(res.choices[0].text)
// jsonString = jsonMatch;
// } else {
// jsonString = jsonMatch[0];
// }
// if (jsonMatch) {
// const promes = {
// fxlb: selectType.value,
// fxmc: value,
// tqjg:JSON.stringify(jsonString),//提取结果
// xyjg: res.choices[0].text,
// ...saveData.value
// }
// yyfxAdd(promes).then(res => {
// proxy.$message({ type: "success", message: "添加成功" });
// emitter.emit('getYyfxSelectPage')
// emitter.emit('getlistData',JSON.stringify(jsonString))
// emitter.emit('showDetails', false)
// }).catch(err => {
// console.log(err, "xxxxx");
// proxy.$message({ type: "error", message: "添加失败" });
// })
// } else {
// proxy.$message({ type: "error", message: "解析失败,请重新上传分析" });
// }
}).catch(err => {
console.log(err, 'vvvv');
}).finally(() => {
loading.value = false
})
})
.catch((err) => {
console.log(err);
})
}
</script>
<style lang="scss" scoped>
.searchTool {
margin-top: 10px;
background-color: #fff;
color: #000;
padding: 16px 20px;
border-radius: 5px;
.searchToolTitle {
font-size: 18px;
font-family: 'verdana';
position: relative;
}
.searchToolContent {
// width: 100%;
margin-top: 16px;
}
.searchToolTitle::after {
content: "";
display: block;
position: absolute;
bottom: 0px;
left: 0px;
width: 80px;
border-radius: 5px;
height: 4px;
background-color: #0386fb;
background: linear-gradient(90deg, #0aa1ff 2%, #ffffff 100%);
}
}
.searchToolFooter {
height: 50px;
display: flex;
justify-content: center;
.establishBut {
width: 200px;
margin-top: 10px;
height: 40px;
}
}
</style>

View File

@ -0,0 +1,64 @@
<template>
<div class="tabDataBox">
<div class="flex titleBox semanticBox" :style="{ height: tableHeight + 'px' }">
<div style=" height: 100%;width: 250px;margin-right: 10px;background-color: #fff;border-radius: 10px;">
<leftList />
</div>
<div style=" height: 100%;width: calc(100% - 262px);">
<div class="headerTitle">警情/案件/情报线索类语义分析</div>
<SearchTool />
<TableType />
</div>
</div>
</div>
</template>
<script setup>
import { onMounted, ref } from 'vue'
import PageTitle from "@/components/aboutTable/PageTitle.vue";
import leftList from './components/leftList.vue';
import SearchTool from './components/searchTool.vue'
import TableType from './components/tableType.vue'
onMounted(() => {
tabHeightFn();
});
const tableHeight = ref()
// 表格高度计算
const tabHeightFn = () => {
tableHeight.value =
window.innerHeight - 150;
window.onresize = function () {
tabHeightFn();
};
};
</script>
<style lang="scss" scoped>
.semanticBox {
background-color: #ffffff00 !important;
}
.headerTitle {
font-size: 24px;
padding: 10px;
font-family: 'verdana';
color: #000;
padding-left: 8px;
position: relative;
}
.headerTitle::after {
content: "";
display: block;
position: absolute;
top: 14px;
left: 0px;
width: 4px;
border-radius: 5px;
height: 31px;
background-color: #0386fb
}
</style>

View File

@ -0,0 +1,169 @@
<template>
<div class="statistical-analysis">
<!-- 左侧树形菜单 -->
<div class="left-menu">
<leftList />
</div>
<!-- 右侧内容区 -->
<div class="right-content" v-if="showDetails">
<div class="btnsBox">
<div class="headerTitle">{{ title }}类语义分析</div>
</div>
<div>
<SearchTool :dict="{ D_GSXT_FXLB }" :title="title" />
</div>
<TableType :dict="{ JQLB, JQLX, JQXL, JQZL, D_GS_XS_LX }" :title="title" />
</div>
<div class="right-content details" v-else>
<ShowDetails :listData="listData" :title="title" />
</div>
</div>
<!-- 详情 -->
</template>
<script setup>
import leftList from './components/leftList.vue';
import SearchTool from './components/searchTool.vue'
import TableType from './components/tableType.vue'
import { getCurrentInstance, onMounted, ref } from 'vue'
import ShowDetails from './components/showDetails.vue';
import emitter from "@/utils/eventBus.js";
const { proxy } = getCurrentInstance();
const { D_GSXT_FXLB, JQLB, JQLX, JQXL, JQZL, D_GS_XS_LX } = proxy.$dict("D_GSXT_FXLB", "JQLB", "JQLX", "JQXL", "JQZL", "D_GS_XS_LX")
const showDetails = ref(true)
const title = ref('警情')
const listData = ref()
onMounted(() => {
emitter.on('showDetails', (res) => {
showDetails.value = res
})
emitter.on('showDetailsTitle', (res) => {
title.value = res
})
emitter.on('getlistData', (res) => {
listData.value = res
})
})
</script>
<style lang="scss" scoped>
.statistical-analysis {
width: 100%;
height: 100%;
.left-menu {
float: left;
width: 280px;
height: calc(100% - 10px);
padding: 20px 4px;
margin-top: 20px;
border-radius: 4px;
background-color: #fff;
border-right: 1px solid #e8e8e8;
color: #333;
line-height: 32px;
::v-deep .checkBox {
flex-direction: column;
.checkall {
margin: 0;
}
}
::v-deep .el-checkbox-group {
display: flex;
flex-direction: column;
}
::v-deep .is-checked {
background: rgb(242, 249, 255);
margin-bottom: 4px;
}
::v-deep .el-checkbox {
padding-left: 8px;
margin-right: 4px;
}
.all {
width: calc(100% - 4px);
}
}
.right-content {
float: left;
width: calc(100% - 290px);
height: 100%;
margin-top: 20px;
margin-left: 10px;
border-radius: 4px;
box-sizing: border-box;
.btnsBox {
background: #fff;
padding: 10px 5px;
border-radius: 4px;
margin-bottom: 10px;
}
.cntlsit {
display: flex;
flex-wrap: wrap;
align-content: start;
gap: 22px;
overflow: hidden;
overflow-y: auto;
background: #fff;
padding: 4px;
box-sizing: border-box;
.cntItem {
width: 300px;
border: 1px solid #ccc;
color: #787878;
border-radius: 4px;
overflow: hidden;
.foot {
text-align: right;
margin-top: 4px;
border-top: 1px solid #ccc;
padding: 4px;
box-sizing: border-box;
}
}
}
}
.details {
background-color: #fff;
height: 99%;
}
}
.headerTitle {
font-size: 24px;
padding: 10px;
font-family: 'verdana';
color: #000;
padding-left: 8px;
position: relative;
}
.headerTitle::after {
content: "";
display: block;
position: absolute;
top: 14px;
left: 0px;
width: 4px;
border-radius: 5px;
height: 31px;
background-color: #0386fb
}
</style>