feat: 新增AI监控仪表盘前端页面和功能

- 新增AI监控仪表盘入口页面(DashEntry.vue)
- 新增AI监控相关API(ai-dash-entry)
- 新增导入对话框组件(ImportDialog)
- 各模块页面新增导入按钮和功能
- 优化国际化配置和路由权限

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
tangweijie 2026-01-21 00:19:30 +08:00
parent 6c6c946b04
commit e8d78a1aea
21 changed files with 661 additions and 179 deletions

View File

@ -0,0 +1,71 @@
import request from '@/config/axios'
/** 风险分布数据项 */
export interface RiskDistributionVO {
name: string
value: number
color: string
}
/** 风险趋势数据项 */
export interface RiskTrendVO {
month: string
highRisk: number
warning: number
normal: number
}
/** AI心航360°统计数据 */
export interface AiDashEntryStatisticsVO {
// 统计卡片
totalCount: number
monthlyNewCount: number
monthlyChange: number
highRiskCount: number
highRiskMonthlyNew: number
highRiskMonthlyChange: number
warningCount: number
warningMonthlyNew: number
warningMonthlyChange: number
normalCount: number
normalMonthlyNew: number
normalMonthlyChange: number
// 图表数据
riskDistribution: RiskDistributionVO[]
riskTrendData: RiskTrendVO[]
}
/** 重点关注对象 */
export interface FocusPersonVO {
id: number
name: string
gender: string
age: number
riskLevelType: string
riskLevel: string
supervisionArea: string
psychologicalRiskLevel: string
isNew: boolean
}
/** 重点关注对象分页请求 */
export interface FocusPersonPageReqVO {
pageNo: number
pageSize: number
riskLevelType?: string
name?: string
areaId?: number
}
/** AI心航360° API */
export const AiDashEntryApi = {
/** 获取AI心航360°统计数据 */
getStatistics: async (): Promise<AiDashEntryStatisticsVO> => {
return await request.get({ url: '/prison/dashboard/ai-dash-entry/statistics' })
},
/** 获取重点关注对象分页列表 */
getFocusPersonPage: async (params: FocusPersonPageReqVO) => {
return await request.get({ url: '/prison/dashboard/ai-dash-entry/focus-person-page', params })
}
}

View File

@ -90,5 +90,17 @@ export const ConsumptionApi = {
// 导出消费订单 Excel // 导出消费订单 Excel
exportConsumption: async (params: ConsumptionPageParams) => { exportConsumption: async (params: ConsumptionPageParams) => {
return await request.download({ url: `/prison/consumption/export-excel`, params }) return await request.download({ url: `/prison/consumption/export-excel`, params })
},
// 获取导入模板
getImportTemplate: async () => {
return await request.download({ url: `/prison/consumption/get-import-template` })
},
// 导入消费记录
importConsumption: async (file: File) => {
const formData = new FormData()
formData.append('file', file)
return await request.upload({ url: `/prison/consumption/import`, data: formData })
} }
} }

View File

@ -119,5 +119,10 @@ export const RiskApi = {
// 导出风险评估 Excel // 导出风险评估 Excel
exportRisk: async (params) => { exportRisk: async (params) => {
return await request.download({ url: `/prison/risk/export-excel`, params }) return await request.download({ url: `/prison/risk/export-excel`, params })
},
// 获取导入模板
getImportTemplate: async () => {
return await request.download({ url: `/prison/risk/get-import-template` })
} }
} }

View File

@ -72,5 +72,10 @@ export const RiskAssessmentApi = {
// 导出危险评估 Excel // 导出危险评估 Excel
exportRiskAssessment: async (params: RiskAssessmentPageParams) => { exportRiskAssessment: async (params: RiskAssessmentPageParams) => {
return await request.download({ url: `/prison/risk-assessment/export-excel`, params }) return await request.download({ url: `/prison/risk-assessment/export-excel`, params })
},
// 获取导入模板
getImportTemplate: async () => {
return await request.download({ url: `/prison/risk-assessment/get-import-template` })
} }
} }

View File

@ -74,5 +74,10 @@ export const ScoreApi = {
// 导出计分考核 Excel // 导出计分考核 Excel
exportScore: async (params: ScorePageParams) => { exportScore: async (params: ScorePageParams) => {
return await request.download({ url: `/prison/score/export-excel`, params }) return await request.download({ url: `/prison/score/export-excel`, params })
},
// 获取导入模板
getImportTemplate: async () => {
return await request.download({ url: `/prison/score/get-import-template` })
} }
} }

View File

@ -114,6 +114,11 @@ export const SituationApi = {
return await request.download({ url: `/prison/situation/export-excel`, params }) return await request.download({ url: `/prison/situation/export-excel`, params })
}, },
// 获取导入模板
getImportTemplate: async () => {
return await request.download({ url: `/prison/situation/get-import-template` })
},
// 导出 AreaApi 供页面使用 // 导出 AreaApi 供页面使用
AreaApi AreaApi
} }

View File

@ -169,6 +169,11 @@ export const WarningApi = {
return await request.download({ url: `/prison/warning/export-excel`, params }) return await request.download({ url: `/prison/warning/export-excel`, params })
}, },
// 获取导入模板
getImportTemplate: async () => {
return await request.download({ url: `/prison/warning/get-import-template` })
},
// 导出 AreaApi 供页面使用 // 导出 AreaApi 供页面使用
AreaApi AreaApi
} }

BIN
src/assets/imgs/avatar.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

View File

@ -0,0 +1,163 @@
<template>
<Dialog v-model="dialogVisible" :title="title" width="400px">
<el-upload
ref="uploadRef"
v-model:file-list="fileList"
:action="importUrl"
:headers="uploadHeaders"
:auto-upload="false"
:limit="1"
:on-exceed="handleExceed"
:on-success="handleSuccess"
:on-error="handleError"
:accept="accept"
drag
>
<Icon icon="ep:upload-filled" class="text-48px text-gray-400" />
<div class="el-upload__text">
将文件拖到此处<em>点击上传</em>
</div>
<template #tip>
<div class="el-upload__tip text-center">
<div class="el-upload__tip">
<el-checkbox v-model="updateSupport" v-if="showUpdateSupport" />
是否更新已经存在的数据
</div>
<span>仅允许导入 xlsxlsx 格式文件</span>
<el-link
v-if="templateUrl"
type="primary"
:underline="false"
style="font-size: 12px; vertical-align: baseline"
@click="downloadTemplate"
>
下载模板
</el-link>
</div>
</template>
</el-upload>
<template #footer>
<el-button @click="dialogVisible = false"> </el-button>
<el-button type="primary" :disabled="fileList.length === 0" :loading="loading" @click="submitFileForm">
</el-button>
</template>
</Dialog>
</template>
<script setup lang="ts">
import { getAccessToken, getTenantId } from '@/utils/auth'
import download from '@/utils/download'
defineOptions({ name: 'ImportDialog' })
const props = defineProps<{
importUrl: string //
templateUrl?: string //
templateName?: string //
title?: string //
showUpdateSupport?: boolean //
accept?: string //
}>()
const emit = defineEmits(['success'])
const message = useMessage()
const dialogVisible = ref(false)
const loading = ref(false)
const updateSupport = ref(false)
const fileList = ref<any[]>([])
const uploadRef = ref()
/** 上传请求头 - 使用 ref 而不是 computed在提交时动态设置 */
const uploadHeaders = ref<Record<string, string>>({})
/** 打开弹窗 */
const open = () => {
dialogVisible.value = true
fileList.value = []
updateSupport.value = false
}
defineExpose({ open })
/** 文件数量超出限制 */
const handleExceed = () => {
message.error('最多只能上传一个文件!')
}
/** 上传成功 */
const handleSuccess = (response: any) => {
loading.value = false
if (response.code === 0) {
const data = response.data
let text = `导入成功 ${data.successCount}`
if (data.failureCount > 0) {
text += `,失败 ${data.failureCount}`
//
if (data.failureRecords && Object.keys(data.failureRecords).length > 0) {
const failDetails = Object.entries(data.failureRecords)
.map(([row, reason]) => `${row}行:${reason}`)
.join('\n')
message.alert(`${text}\n\n失败详情\n${failDetails}`)
} else {
message.warning(text)
}
} else {
message.success(text)
}
dialogVisible.value = false
emit('success')
} else {
message.error(response.msg || '导入失败')
}
}
/** 上传失败 */
const handleError = () => {
loading.value = false
message.error('上传失败,请检查文件格式')
}
/** 提交表单 */
const submitFileForm = () => {
if (fileList.value.length === 0) {
message.warning('请选择要上传的文件')
return
}
// token tenant-id
const headers: Record<string, string> = {
Authorization: 'Bearer ' + getAccessToken()
}
const tenantId = getTenantId()
if (tenantId) {
headers['tenant-id'] = String(tenantId)
}
uploadHeaders.value = headers
loading.value = true
uploadRef.value?.submit()
}
/** 下载模板 */
const downloadTemplate = async () => {
if (!props.templateUrl) return
try {
// API VITE_BASE_URL + VITE_API_URL + templateUrl
const apiUrl = import.meta.env.VITE_BASE_URL + (import.meta.env.VITE_API_URL || '/admin-api') + props.templateUrl
const response = await fetch(apiUrl, {
headers: {
Authorization: 'Bearer ' + getAccessToken(),
'tenant-id': getTenantId() || ''
}
})
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
const blob = await response.blob()
download.excel(blob, props.templateName || '导入模板.xls')
} catch (e) {
console.error('下载模板失败:', e)
message.error('下载模板失败')
}
}
</script>

View File

@ -82,14 +82,18 @@ service.interceptors.request.use(
const isFormUrlEncoded = const isFormUrlEncoded =
contentType === 'application/x-www-form-urlencoded' || contentType === 'application/x-www-form-urlencoded' ||
contentType.includes('application/x-www-form-urlencoded') contentType.includes('application/x-www-form-urlencoded')
const isMultipartFormData =
contentType === 'multipart/form-data' ||
contentType.includes('multipart/form-data')
if (isFormUrlEncoded) { if (isFormUrlEncoded) {
// 使用表单序列化 // 使用表单序列化
if (config.data && typeof config.data !== 'string') { if (config.data && typeof config.data !== 'string') {
config.data = qs.stringify(config.data, { allowDots: true, indices: false }) config.data = qs.stringify(config.data, { allowDots: true, indices: false })
} }
} else { } else if (!isMultipartFormData) {
// 默认使用 JSON 序列化,确保数组被正确序列化为 JSON 数组 // 默认使用 JSON 序列化,确保数组被正确序列化为 JSON 数组
// 这包括 'application/json' 以及其他情况 // 这包括 'application/json' 以及其他情况
// 注意multipart/form-data 类型不进行转换,需要保持 FormData 对象原样
if (config.data && typeof config.data === 'object') { if (config.data && typeof config.data === 'object') {
config.data = JSON.stringify(config.data) config.data = JSON.stringify(config.data)
config.headers['Content-Type'] = 'application/json' config.headers['Content-Type'] = 'application/json'

View File

@ -113,8 +113,8 @@ export default {
small: 'Small' small: 'Small'
}, },
login: { login: {
welcome: 'Welcome to the system', welcome: 'Welcome to AI Xinhang 360°',
message: 'Backstage management system', message: 'Focusing on psychological needs of different individuals, building a full-process, intelligent one-stop psychological evaluation service platform, making psychological assessment more professional, efficient, and caring.',
tenantname: 'TenantName', tenantname: 'TenantName',
username: 'Username', username: 'Username',
password: 'Password', password: 'Password',

View File

@ -114,8 +114,8 @@ export default {
small: '小' small: '小'
}, },
login: { login: {
welcome: '欢迎使用本系统', welcome: '欢迎使用AI心航360°',
message: '开箱即用的中后台管理系统', message: '聚焦不同人员心理需求,构建全流程、智能化的一站式心理测评服务平台,让心理评估更专业、更高效、更贴心。',
tenantname: '租户名称', tenantname: '租户名称',
username: '用户名', username: '用户名',
password: '密码', password: '密码',
@ -470,4 +470,4 @@ export default {
} }
}, },
'OAuth 2.0': 'OAuth 2.0' // 避免菜单名是 OAuth 2.0 时,一直 warn 报错 'OAuth 2.0': 'OAuth 2.0' // 避免菜单名是 OAuth 2.0 时,一直 warn 报错
} }

View File

@ -55,18 +55,13 @@ const whiteList = [
'/register', '/register',
'/oauthLogin/gitee', '/oauthLogin/gitee',
'/dashboard', // Dashboard 页面 '/dashboard', // Dashboard 页面
'/dashentry' // DashEntry 页面 '/ai-dash-entry' // DashEntry 页面
] ]
// 路由加载前 // 路由加载前
router.beforeEach(async (to, from, next) => { router.beforeEach(async (to, from, next) => {
start() start()
loadStart() loadStart()
// 如果是主页路径或 dashboard 路径,直接放行(跳过权限验证)
if (to.path === '/dashboard' || to.path === '/dashentry') {
next()
return
}
if (getAccessToken()) { if (getAccessToken()) {
if (to.path === '/login') { if (to.path === '/login') {
next({ path: '/' }) next({ path: '/' })
@ -100,6 +95,10 @@ router.beforeEach(async (to, from, next) => {
} }
} else { } else {
if (whiteList.indexOf(to.path) !== -1) { if (whiteList.indexOf(to.path) !== -1) {
const permissionStore = usePermissionStoreWithOut()
if (permissionStore.getRouters.length === 0) {
await permissionStore.generateRoutes()
}
next() next()
} else { } else {
next(`/login?redirect=${to.fullPath}`) // 否则全部重定向到登录页 next(`/login?redirect=${to.fullPath}`) // 否则全部重定向到登录页

View File

@ -59,7 +59,7 @@ const remainingRouter: AppRouteRecordRaw[] = [
children: [ children: [
{ {
path: 'index', path: 'index',
component: () => import('@/views/Home/Index.vue'), component: () => import('@/views/DashEntry/DashEntry.vue'),
name: 'Index', name: 'Index',
meta: { meta: {
title: t('router.home'), title: t('router.home'),
@ -186,16 +186,6 @@ const remainingRouter: AppRouteRecordRaw[] = [
} }
}, },
{
path: '/dashentry',
component: () => import('@/views/DashEntry/DashEntry.vue'),
name: 'DashEntry',
meta: {
hidden: true,
title: 'DashEntry',
noTagsView: true
}
},
{ {
path: '/login', path: '/login',
component: () => import('@/views/Login/Login.vue'), component: () => import('@/views/Login/Login.vue'),

View File

@ -42,7 +42,7 @@
<!-- 底部表格 --> <!-- 底部表格 -->
<div class="table-section"> <div class="table-section">
<div class="table-title">重点关注对象列表</div> <div class="table-title">重点关注对象列表</div>
<el-table :data="paginatedResults" style="width: 100%" stripe> <el-table :data="tableData.results" style="width: 100%" stripe v-loading="loading">
<el-table-column prop="name" label="姓名" width="`16.3%`" class-name="name-column"> <el-table-column prop="name" label="姓名" width="`16.3%`" class-name="name-column">
<template #default="{ row }"> <template #default="{ row }">
<span v-if="row.isNew" class="new-tag" :class="`tag-${row.riskLevelType}`">新增</span> <span v-if="row.isNew" class="new-tag" :class="`tag-${row.riskLevelType}`">新增</span>
@ -83,226 +83,276 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed } from 'vue' import { ref, computed, onMounted } from 'vue'
import type { EChartsOption } from 'echarts' import type { EChartsOption } from 'echarts'
// @ts-ignore // @ts-ignore
import EChart from '@/components/Echart/src/Echart.vue' import EChart from '@/components/Echart/src/Echart.vue'
import { Icon } from '@/components/Icon' import { Icon } from '@/components/Icon'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { AiDashEntryApi, type AiDashEntryStatisticsVO, type FocusPersonVO } from '@/api/prison/ai-dash-entry'
import { ElMessage } from 'element-plus'
defineOptions({ name: 'DashEntry' }) defineOptions({ name: 'DashEntry' })
const router = useRouter() const router = useRouter()
const loading = ref(false)
//
const statistics = ref<AiDashEntryStatisticsVO>({
totalCount: 0,
monthlyNewCount: 0,
monthlyChange: 0,
highRiskCount: 0,
highRiskMonthlyNew: 0,
highRiskMonthlyChange: 0,
warningCount: 0,
warningMonthlyNew: 0,
warningMonthlyChange: 0,
normalCount: 0,
normalMonthlyNew: 0,
normalMonthlyChange: 0,
riskDistribution: [],
riskTrendData: []
})
// //
const statsCards = ref([ const statsCards = computed(() => [
{ {
title: '全部人员', title: '全部人员',
value: '59人', value: `${statistics.value.totalCount}`,
subtitle: '本月45人 +15', subtitle: `本月${statistics.value.monthlyNewCount}${statistics.value.monthlyChange >= 0 ? '+' : ''}${statistics.value.monthlyChange}`,
trend: 'up', trend: statistics.value.monthlyChange >= 0 ? 'up' : 'down',
type: 'all', type: 'all',
icon: 'ep:user' icon: 'ep:user'
}, },
{ {
title: '高危人员', title: '高危人员',
value: '12人', value: `${statistics.value.highRiskCount}`,
subtitle: '本月3人 -3', subtitle: `本月${statistics.value.highRiskMonthlyNew}${statistics.value.highRiskMonthlyChange >= 0 ? '+' : ''}${statistics.value.highRiskMonthlyChange}`,
trend: 'down', trend: statistics.value.highRiskMonthlyChange >= 0 ? 'up' : 'down',
type: 'high', type: 'high',
icon: 'ep:warning-filled' icon: 'ep:warning-filled'
}, },
{ {
title: '预警人员', title: '预警人员',
value: '23人', value: `${statistics.value.warningCount}`,
subtitle: '本月4人 +2', subtitle: `本月${statistics.value.warningMonthlyNew}${statistics.value.warningMonthlyChange >= 0 ? '+' : ''}${statistics.value.warningMonthlyChange}`,
trend: 'up', trend: statistics.value.warningMonthlyChange >= 0 ? 'up' : 'down',
type: 'warning', type: 'warning',
icon: 'ep:bell' icon: 'ep:bell'
}, },
{ {
title: '普通人员', title: '普通人员',
value: '32人', value: `${statistics.value.normalCount}`,
subtitle: '本月5人 +1', subtitle: `本月${statistics.value.normalMonthlyNew}${statistics.value.normalMonthlyChange >= 0 ? '+' : ''}${statistics.value.normalMonthlyChange}`,
trend: 'up', trend: statistics.value.normalMonthlyChange >= 0 ? 'up' : 'down',
type: 'normal', type: 'normal',
icon: 'ep:user-filled' icon: 'ep:user-filled'
} }
]) ])
// //
const riskDistributionOptions = computed<EChartsOption>(() => ({ const riskDistributionOptions = computed<EChartsOption>(() => {
tooltip: { const distribution = statistics.value.riskDistribution || []
trigger: 'item', const total = distribution.reduce((sum, item) => sum + item.value, 0)
formatter: '{b}: {c} ({d}%)'
}, const data = distribution.map(item => {
legend: { const percentage = total > 0 ? Math.round((item.value / total) * 100) : 0
show: true, return {
bottom: 0, value: item.value,
left: 'center', name: `${item.name} (${percentage}%)`,
itemGap: 20, itemStyle: { color: item.color }
icon: 'circle', }
textStyle: { })
fontSize: 12
return {
tooltip: {
trigger: 'item',
formatter: '{b}: {c} ({d}%)'
}, },
data: ['普通 (80%)', '预警 (15%)', '高危 (5%)'] legend: {
}, show: true,
series: [ bottom: 0,
{ left: 'center',
name: '风险等级分布', itemGap: 20,
type: 'pie', icon: 'circle',
radius: ['40%', '70%'], textStyle: {
center: ['50%', '50%'], fontSize: 12
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 10,
borderColor: '#fff',
borderWidth: 2
}, },
label: { data: data.map(item => item.name)
show: true, },
formatter: '{b}\n{d}%' series: [
}, {
emphasis: { name: '风险等级分布',
type: 'pie',
radius: ['40%', '70%'],
center: ['50%', '50%'],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 10,
borderColor: '#fff',
borderWidth: 2
},
label: { label: {
show: true, show: true,
fontSize: 12, formatter: '{b}\n{d}%'
fontWeight: 'bold' },
} emphasis: {
}, label: {
data: [ show: true,
{ value: 978, name: '普通 (80%)', itemStyle: { color: '#5470c6' } }, fontSize: 12,
{ value: 189, name: '预警 (15%)', itemStyle: { color: '#fac858' } }, fontWeight: 'bold'
{ value: 67, name: '高危 (5%)', itemStyle: { color: '#ee6666' } } }
] },
} data: data
] }
})) ]
}
})
// 线 // 线
const riskTrendOptions = computed<EChartsOption>(() => ({ const riskTrendOptions = computed<EChartsOption>(() => {
tooltip: { const trendData = statistics.value.riskTrendData || []
trigger: 'axis' const months = trendData.map(item => item.month)
}, const highRiskData = trendData.map(item => item.highRisk)
legend: { const warningData = trendData.map(item => item.warning)
data: ['高危', '预警', '普通'], const normalData = trendData.map(item => item.normal)
bottom: 0
}, // Y
grid: { const allValues = [...highRiskData, ...warningData, ...normalData]
left: '3%', const maxValue = Math.max(...allValues, 10)
right: '4%', const yAxisMax = Math.ceil(maxValue / 50) * 50 + 50
bottom: '15%',
containLabel: true return {
}, tooltip: {
xAxis: { trigger: 'axis'
type: 'category',
boundaryGap: false,
data: ['2024-10', '2024-11', '2024-12', '2025-01', '2025-02', '2025-03', '2025-04']
},
yAxis: {
type: 'value',
min: 0,
max: 300,
interval: 50
},
series: [
{
name: '高危',
type: 'line',
data: [50, 52, 48, 51, 49, 50, 48],
itemStyle: { color: '#ee6666' },
lineStyle: { color: '#ee6666' },
symbol: 'circle',
symbolSize: 6
}, },
{ legend: {
name: '预警', data: ['高危', '预警', '普通'],
type: 'line', bottom: 0
data: [150, 180, 170, 190, 185, 175, 180],
itemStyle: { color: '#fac858' },
lineStyle: { color: '#fac858', type: 'dashed' },
symbol: 'circle',
symbolSize: 6
}, },
{ grid: {
name: '普通', left: '3%',
type: 'line', right: '4%',
data: [250, 280, 270, 290, 285, 275, 280], bottom: '15%',
itemStyle: { color: '#666' }, containLabel: true
lineStyle: { color: '#666' }, },
symbol: 'circle', xAxis: {
symbolSize: 6 type: 'category',
} boundaryGap: false,
] data: months
})) },
yAxis: {
type: 'value',
min: 0,
max: yAxisMax,
interval: Math.ceil(yAxisMax / 6)
},
series: [
{
name: '高危',
type: 'line',
data: highRiskData,
itemStyle: { color: '#ee6666' },
lineStyle: { color: '#ee6666' },
symbol: 'circle',
symbolSize: 6
},
{
name: '预警',
type: 'line',
data: warningData,
itemStyle: { color: '#fac858' },
lineStyle: { color: '#fac858', type: 'dashed' },
symbol: 'circle',
symbolSize: 6
},
{
name: '普通',
type: 'line',
data: normalData,
itemStyle: { color: '#666' },
lineStyle: { color: '#666' },
symbol: 'circle',
symbolSize: 6
}
]
}
})
// //
const tableData = ref({ const tableData = ref<{
current: number
total: number
results: FocusPersonVO[]
}>({
current: 1, current: 1,
total: 3, total: 0,
results: [ results: []
{
name: '王建国',
isNew: true,
gender: '男',
age: 34,
riskLevel: '高危',
riskLevelType: 'high',
supervisionArea: '第一监区',
psychologicalRiskLevel: '一级风险'
},
{
name: '李秀英',
isNew: true,
gender: '女',
age: 29,
riskLevel: '预警',
riskLevelType: 'warning',
supervisionArea: '第二监区',
psychologicalRiskLevel: '二级风险'
},
{
name: '张伟',
isNew: false,
gender: '男',
age: 41,
riskLevel: '普通',
riskLevelType: 'normal',
supervisionArea: '第一监区',
psychologicalRiskLevel: '三级风险'
}
]
}) })
// //
const pageSize = ref(10) const pageSize = ref(10)
//
const paginatedResults = computed(() => {
const start = (tableData.value.current - 1) * pageSize.value
const end = start + pageSize.value
return tableData.value.results.slice(start, end)
})
// //
const handleSizeChange = (val: number) => { const handleSizeChange = (val: number) => {
pageSize.value = val pageSize.value = val
tableData.value.current = 1 // tableData.value.current = 1
loadFocusPersonPage()
} }
// //
const handleCurrentChange = (val: number) => { const handleCurrentChange = (val: number) => {
tableData.value.current = val tableData.value.current = val
loadFocusPersonPage()
} }
const handleView = (row: any) => { const handleView = (row: FocusPersonVO) => {
router.push({ router.push({
name: 'Dashboard', path: '/prisoner/prisoner/dashboard',
query: { query: {
id: row.id || row.name prisonerId: row.id
} }
}) })
} }
//
const loadStatistics = async () => {
try {
const data = await AiDashEntryApi.getStatistics()
if (data) {
statistics.value = data
}
} catch (error) {
console.error('加载统计数据失败:', error)
ElMessage.error('加载统计数据失败')
}
}
//
const loadFocusPersonPage = async () => {
loading.value = true
try {
const data = await AiDashEntryApi.getFocusPersonPage({
pageNo: tableData.value.current,
pageSize: pageSize.value
})
if (data) {
tableData.value.results = data.list || []
tableData.value.total = data.total || 0
}
} catch (error) {
console.error('加载重点关注对象失败:', error)
ElMessage.error('加载重点关注对象列表失败')
} finally {
loading.value = false
}
}
onMounted(() => {
loadStatistics()
loadFocusPersonPage()
})
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">

View File

@ -67,6 +67,14 @@
> >
<Icon icon="ep:plus" class="mr-5px" /> 新增 <Icon icon="ep:plus" class="mr-5px" /> 新增
</el-button> </el-button>
<el-button
type="warning"
plain
@click="handleImport"
v-hasPermi="['prison:consumption:import']"
>
<Icon icon="ep:upload" class="mr-5px" /> 导入
</el-button>
<el-button <el-button
type="success" type="success"
plain plain
@ -166,6 +174,16 @@
<!-- 明细查看弹窗 --> <!-- 明细查看弹窗 -->
<ConsumptionDetailDialog ref="detailDialogRef" /> <ConsumptionDetailDialog ref="detailDialogRef" />
<!-- 导入弹窗 -->
<ImportDialog
ref="importDialogRef"
:import-url="getImportUrl()"
template-url="/prison/consumption/get-import-template"
template-name="消费记录导入模板.xls"
title="导入消费记录"
@success="getList"
/>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
@ -175,6 +193,7 @@ import download from '@/utils/download'
import { ConsumptionApi, Consumption } from '@/api/prison/consumption' import { ConsumptionApi, Consumption } from '@/api/prison/consumption'
import ConsumptionForm from './ConsumptionForm.vue' import ConsumptionForm from './ConsumptionForm.vue'
import ConsumptionDetailDialog from './ConsumptionDetailDialog.vue' import ConsumptionDetailDialog from './ConsumptionDetailDialog.vue'
import ImportDialog from '@/components/ImportDialog/index.vue'
defineOptions({ name: 'Consumption' }) defineOptions({ name: 'Consumption' })
@ -273,6 +292,15 @@ const handleExport = async () => {
} }
} }
/** 导入操作 */
const importDialogRef = ref()
const handleImport = () => {
importDialogRef.value.open()
}
const getImportUrl = () => {
return import.meta.env.VITE_BASE_URL + (import.meta.env.VITE_API_URL || '/admin-api') + '/prison/consumption/import'
}
/** 初始化 */ /** 初始化 */
onMounted(() => { onMounted(() => {
getList() getList()

View File

@ -76,6 +76,14 @@
> >
<Icon icon="ep:plus" class="mr-5px" /> 新增评估 <Icon icon="ep:plus" class="mr-5px" /> 新增评估
</el-button> </el-button>
<el-button
type="warning"
plain
@click="handleImport"
v-hasPermi="['prison:risk:import']"
>
<Icon icon="ep:upload" class="mr-5px" /> 导入
</el-button>
<el-button <el-button
type="success" type="success"
plain plain
@ -172,6 +180,16 @@
<!-- 表单弹窗添加/修改 --> <!-- 表单弹窗添加/修改 -->
<RiskForm ref="formRef" @success="getList" /> <RiskForm ref="formRef" @success="getList" />
<!-- 导入弹窗 -->
<ImportDialog
ref="importDialogRef"
:import-url="getImportUrl()"
template-url="/prison/risk/get-import-template"
template-name="风险评估导入模板.xls"
title="导入风险评估"
@success="getList"
/>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
@ -180,6 +198,7 @@ import { formatDateTime, formatDate } from '@/utils/formatTime'
import download from '@/utils/download' import download from '@/utils/download'
import { RiskApi, RiskPageReqVO } from '@/api/prison/risk' import { RiskApi, RiskPageReqVO } from '@/api/prison/risk'
import RiskForm from './RiskForm.vue' import RiskForm from './RiskForm.vue'
import ImportDialog from '@/components/ImportDialog/index.vue'
defineOptions({ name: 'PrisonRisk' }) defineOptions({ name: 'PrisonRisk' })
@ -275,6 +294,15 @@ const handleExport = async () => {
} }
} }
/** 导入操作 */
const importDialogRef = ref()
const handleImport = () => {
importDialogRef.value.open()
}
const getImportUrl = () => {
return import.meta.env.VITE_BASE_URL + (import.meta.env.VITE_API_URL || '/admin-api') + '/prison/risk/import'
}
/** 初始化 */ /** 初始化 */
onMounted(() => { onMounted(() => {
getList() getList()

View File

@ -82,6 +82,14 @@
> >
<Icon icon="ep:plus" class="mr-5px" /> 新增 <Icon icon="ep:plus" class="mr-5px" /> 新增
</el-button> </el-button>
<el-button
type="warning"
plain
@click="handleImport"
v-hasPermi="['prison:risk-assessment:import']"
>
<Icon icon="ep:upload" class="mr-5px" /> 导入
</el-button>
<el-button <el-button
type="success" type="success"
plain plain
@ -178,6 +186,16 @@
<!-- 表单弹窗添加/修改 --> <!-- 表单弹窗添加/修改 -->
<RiskAssessmentForm ref="formRef" @success="getList" /> <RiskAssessmentForm ref="formRef" @success="getList" />
<!-- 导入弹窗 -->
<ImportDialog
ref="importDialogRef"
:import-url="getImportUrl()"
template-url="/prison/risk-assessment/get-import-template"
template-name="危险评估导入模板.xls"
title="导入危险评估"
@success="getList"
/>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
@ -186,6 +204,7 @@ import { formatDateTime } from '@/utils/formatTime'
import download from '@/utils/download' import download from '@/utils/download'
import { RiskAssessmentApi, RiskAssessment } from '@/api/prison/riskassessment' import { RiskAssessmentApi, RiskAssessment } from '@/api/prison/riskassessment'
import RiskAssessmentForm from './RiskAssessmentForm.vue' import RiskAssessmentForm from './RiskAssessmentForm.vue'
import ImportDialog from '@/components/ImportDialog/index.vue'
defineOptions({ name: 'RiskAssessment' }) defineOptions({ name: 'RiskAssessment' })
@ -281,6 +300,15 @@ const handleExport = async () => {
} }
} }
/** 导入操作 */
const importDialogRef = ref()
const handleImport = () => {
importDialogRef.value.open()
}
const getImportUrl = () => {
return import.meta.env.VITE_BASE_URL + (import.meta.env.VITE_API_URL || '/admin-api') + '/prison/risk-assessment/import'
}
/** 初始化 */ /** 初始化 */
onMounted(() => { onMounted(() => {
getList() getList()

View File

@ -76,6 +76,14 @@
> >
<Icon icon="ep:plus" class="mr-5px" /> 新增 <Icon icon="ep:plus" class="mr-5px" /> 新增
</el-button> </el-button>
<el-button
type="warning"
plain
@click="handleImport"
v-hasPermi="['prison:score:import']"
>
<Icon icon="ep:upload" class="mr-5px" /> 导入
</el-button>
<el-button <el-button
type="success" type="success"
plain plain
@ -164,6 +172,16 @@
<!-- 表单弹窗添加/修改 --> <!-- 表单弹窗添加/修改 -->
<ScoreForm ref="formRef" @success="getList" /> <ScoreForm ref="formRef" @success="getList" />
<!-- 导入弹窗 -->
<ImportDialog
ref="importDialogRef"
:import-url="getImportUrl()"
template-url="/prison/score/get-import-template"
template-name="计分考核导入模板.xls"
title="导入计分考核"
@success="getList"
/>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
@ -172,6 +190,7 @@ import { formatDateTime } from '@/utils/formatTime'
import download from '@/utils/download' import download from '@/utils/download'
import { ScoreApi, Score } from '@/api/prison/score' import { ScoreApi, Score } from '@/api/prison/score'
import ScoreForm from './ScoreForm.vue' import ScoreForm from './ScoreForm.vue'
import ImportDialog from '@/components/ImportDialog/index.vue'
defineOptions({ name: 'Score' }) defineOptions({ name: 'Score' })
@ -266,6 +285,15 @@ const handleExport = async () => {
} }
} }
/** 导入操作 */
const importDialogRef = ref()
const handleImport = () => {
importDialogRef.value.open()
}
const getImportUrl = () => {
return import.meta.env.VITE_BASE_URL + (import.meta.env.VITE_API_URL || '/admin-api') + '/prison/score/import'
}
/** 初始化 */ /** 初始化 */
onMounted(() => { onMounted(() => {
getList() getList()

View File

@ -85,6 +85,14 @@
> >
<Icon icon="ep:plus" class="mr-5px" /> 新增狱情 <Icon icon="ep:plus" class="mr-5px" /> 新增狱情
</el-button> </el-button>
<el-button
type="warning"
plain
@click="handleImport"
v-hasPermi="['prison:situation:import']"
>
<Icon icon="ep:upload" class="mr-5px" /> 导入
</el-button>
<el-button <el-button
type="success" type="success"
plain plain
@ -184,6 +192,16 @@
<!-- 表单弹窗添加/修改 --> <!-- 表单弹窗添加/修改 -->
<SituationForm ref="formRef" @success="getList" /> <SituationForm ref="formRef" @success="getList" />
<!-- 导入弹窗 -->
<ImportDialog
ref="importDialogRef"
:import-url="getImportUrl()"
template-url="/prison/situation/get-import-template"
template-name="狱情收集导入模板.xls"
title="导入狱情收集"
@success="getList"
/>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
@ -192,6 +210,7 @@ import { formatDateTime } from '@/utils/formatTime'
import download from '@/utils/download' import download from '@/utils/download'
import { SituationApi, SituationPageReqVO } from '@/api/prison/situation' import { SituationApi, SituationPageReqVO } from '@/api/prison/situation'
import SituationForm from './SituationForm.vue' import SituationForm from './SituationForm.vue'
import ImportDialog from '@/components/ImportDialog/index.vue'
defineOptions({ name: 'PrisonSituation' }) defineOptions({ name: 'PrisonSituation' })
@ -307,6 +326,15 @@ const handleExport = async () => {
} }
} }
/** 导入操作 */
const importDialogRef = ref()
const handleImport = () => {
importDialogRef.value.open()
}
const getImportUrl = () => {
return import.meta.env.VITE_BASE_URL + (import.meta.env.VITE_API_URL || '/admin-api') + '/prison/situation/import'
}
/** 初始化 */ /** 初始化 */
onMounted(() => { onMounted(() => {
getList() getList()

View File

@ -100,6 +100,14 @@
> >
<Icon icon="ep:plus" class="mr-5px" /> 新增预警 <Icon icon="ep:plus" class="mr-5px" /> 新增预警
</el-button> </el-button>
<el-button
type="warning"
plain
@click="handleImport"
v-hasPermi="['prison:warning:import']"
>
<Icon icon="ep:upload" class="mr-5px" /> 导入
</el-button>
<el-button <el-button
type="success" type="success"
plain plain
@ -201,6 +209,16 @@
<!-- 预警处置操作弹窗 --> <!-- 预警处置操作弹窗 -->
<WarningActionForm ref="actionFormRef" @success="getList" /> <WarningActionForm ref="actionFormRef" @success="getList" />
<!-- 导入弹窗 -->
<ImportDialog
ref="importDialogRef"
:import-url="getImportUrl()"
template-url="/prison/warning/get-import-template"
template-name="预警管理导入模板.xls"
title="导入预警信息"
@success="getList"
/>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
@ -210,6 +228,7 @@ import download from '@/utils/download'
import { WarningApi, WarningPageReqVO } from '@/api/prison/warning' import { WarningApi, WarningPageReqVO } from '@/api/prison/warning'
import WarningForm from './WarningForm.vue' import WarningForm from './WarningForm.vue'
import WarningActionForm from './WarningActionForm.vue' import WarningActionForm from './WarningActionForm.vue'
import ImportDialog from '@/components/ImportDialog/index.vue'
defineOptions({ name: 'PrisonWarning' }) defineOptions({ name: 'PrisonWarning' })
@ -324,6 +343,15 @@ const handleExport = async () => {
} }
} }
/** 导入操作 */
const importDialogRef = ref()
const handleImport = () => {
importDialogRef.value.open()
}
const getImportUrl = () => {
return import.meta.env.VITE_BASE_URL + (import.meta.env.VITE_API_URL || '/admin-api') + '/prison/warning/import'
}
/** 初始化 */ /** 初始化 */
onMounted(() => { onMounted(() => {
getList() getList()