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
exportConsumption: async (params: ConsumptionPageParams) => {
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
exportRisk: async (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
exportRiskAssessment: async (params: RiskAssessmentPageParams) => {
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
exportScore: async (params: ScorePageParams) => {
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 })
},
// 获取导入模板
getImportTemplate: async () => {
return await request.download({ url: `/prison/situation/get-import-template` })
},
// 导出 AreaApi 供页面使用
AreaApi
}

View File

@ -169,6 +169,11 @@ export const WarningApi = {
return await request.download({ url: `/prison/warning/export-excel`, params })
},
// 获取导入模板
getImportTemplate: async () => {
return await request.download({ url: `/prison/warning/get-import-template` })
},
// 导出 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 =
contentType === '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 (config.data && typeof config.data !== 'string') {
config.data = qs.stringify(config.data, { allowDots: true, indices: false })
}
} else {
} else if (!isMultipartFormData) {
// 默认使用 JSON 序列化,确保数组被正确序列化为 JSON 数组
// 这包括 'application/json' 以及其他情况
// 注意multipart/form-data 类型不进行转换,需要保持 FormData 对象原样
if (config.data && typeof config.data === 'object') {
config.data = JSON.stringify(config.data)
config.headers['Content-Type'] = 'application/json'

View File

@ -113,8 +113,8 @@ export default {
small: 'Small'
},
login: {
welcome: 'Welcome to the system',
message: 'Backstage management system',
welcome: 'Welcome to AI Xinhang 360°',
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',
username: 'Username',
password: 'Password',

View File

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

View File

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

View File

@ -59,7 +59,7 @@ const remainingRouter: AppRouteRecordRaw[] = [
children: [
{
path: 'index',
component: () => import('@/views/Home/Index.vue'),
component: () => import('@/views/DashEntry/DashEntry.vue'),
name: 'Index',
meta: {
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',
component: () => import('@/views/Login/Login.vue'),

View File

@ -42,7 +42,7 @@
<!-- 底部表格 -->
<div class="table-section">
<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">
<template #default="{ row }">
<span v-if="row.isNew" class="new-tag" :class="`tag-${row.riskLevelType}`">新增</span>
@ -83,226 +83,276 @@
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
import { ref, computed, onMounted } from 'vue'
import type { EChartsOption } from 'echarts'
// @ts-ignore
import EChart from '@/components/Echart/src/Echart.vue'
import { Icon } from '@/components/Icon'
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' })
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: '全部人员',
value: '59人',
subtitle: '本月45人 +15',
trend: 'up',
value: `${statistics.value.totalCount}`,
subtitle: `本月${statistics.value.monthlyNewCount}${statistics.value.monthlyChange >= 0 ? '+' : ''}${statistics.value.monthlyChange}`,
trend: statistics.value.monthlyChange >= 0 ? 'up' : 'down',
type: 'all',
icon: 'ep:user'
},
{
title: '高危人员',
value: '12人',
subtitle: '本月3人 -3',
trend: 'down',
value: `${statistics.value.highRiskCount}`,
subtitle: `本月${statistics.value.highRiskMonthlyNew}${statistics.value.highRiskMonthlyChange >= 0 ? '+' : ''}${statistics.value.highRiskMonthlyChange}`,
trend: statistics.value.highRiskMonthlyChange >= 0 ? 'up' : 'down',
type: 'high',
icon: 'ep:warning-filled'
},
{
title: '预警人员',
value: '23人',
subtitle: '本月4人 +2',
trend: 'up',
value: `${statistics.value.warningCount}`,
subtitle: `本月${statistics.value.warningMonthlyNew}${statistics.value.warningMonthlyChange >= 0 ? '+' : ''}${statistics.value.warningMonthlyChange}`,
trend: statistics.value.warningMonthlyChange >= 0 ? 'up' : 'down',
type: 'warning',
icon: 'ep:bell'
},
{
title: '普通人员',
value: '32人',
subtitle: '本月5人 +1',
trend: 'up',
value: `${statistics.value.normalCount}`,
subtitle: `本月${statistics.value.normalMonthlyNew}${statistics.value.normalMonthlyChange >= 0 ? '+' : ''}${statistics.value.normalMonthlyChange}`,
trend: statistics.value.normalMonthlyChange >= 0 ? 'up' : 'down',
type: 'normal',
icon: 'ep:user-filled'
}
])
//
const riskDistributionOptions = computed<EChartsOption>(() => ({
tooltip: {
trigger: 'item',
formatter: '{b}: {c} ({d}%)'
},
legend: {
show: true,
bottom: 0,
left: 'center',
itemGap: 20,
icon: 'circle',
textStyle: {
fontSize: 12
const riskDistributionOptions = computed<EChartsOption>(() => {
const distribution = statistics.value.riskDistribution || []
const total = distribution.reduce((sum, item) => sum + item.value, 0)
const data = distribution.map(item => {
const percentage = total > 0 ? Math.round((item.value / total) * 100) : 0
return {
value: item.value,
name: `${item.name} (${percentage}%)`,
itemStyle: { color: item.color }
}
})
return {
tooltip: {
trigger: 'item',
formatter: '{b}: {c} ({d}%)'
},
data: ['普通 (80%)', '预警 (15%)', '高危 (5%)']
},
series: [
{
name: '风险等级分布',
type: 'pie',
radius: ['40%', '70%'],
center: ['50%', '50%'],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 10,
borderColor: '#fff',
borderWidth: 2
legend: {
show: true,
bottom: 0,
left: 'center',
itemGap: 20,
icon: 'circle',
textStyle: {
fontSize: 12
},
label: {
show: true,
formatter: '{b}\n{d}%'
},
emphasis: {
data: data.map(item => item.name)
},
series: [
{
name: '风险等级分布',
type: 'pie',
radius: ['40%', '70%'],
center: ['50%', '50%'],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 10,
borderColor: '#fff',
borderWidth: 2
},
label: {
show: true,
fontSize: 12,
fontWeight: 'bold'
}
},
data: [
{ value: 978, name: '普通 (80%)', itemStyle: { color: '#5470c6' } },
{ value: 189, name: '预警 (15%)', itemStyle: { color: '#fac858' } },
{ value: 67, name: '高危 (5%)', itemStyle: { color: '#ee6666' } }
]
}
]
}))
formatter: '{b}\n{d}%'
},
emphasis: {
label: {
show: true,
fontSize: 12,
fontWeight: 'bold'
}
},
data: data
}
]
}
})
// 线
const riskTrendOptions = computed<EChartsOption>(() => ({
tooltip: {
trigger: 'axis'
},
legend: {
data: ['高危', '预警', '普通'],
bottom: 0
},
grid: {
left: '3%',
right: '4%',
bottom: '15%',
containLabel: true
},
xAxis: {
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
const riskTrendOptions = computed<EChartsOption>(() => {
const trendData = statistics.value.riskTrendData || []
const months = trendData.map(item => item.month)
const highRiskData = trendData.map(item => item.highRisk)
const warningData = trendData.map(item => item.warning)
const normalData = trendData.map(item => item.normal)
// Y
const allValues = [...highRiskData, ...warningData, ...normalData]
const maxValue = Math.max(...allValues, 10)
const yAxisMax = Math.ceil(maxValue / 50) * 50 + 50
return {
tooltip: {
trigger: 'axis'
},
{
name: '预警',
type: 'line',
data: [150, 180, 170, 190, 185, 175, 180],
itemStyle: { color: '#fac858' },
lineStyle: { color: '#fac858', type: 'dashed' },
symbol: 'circle',
symbolSize: 6
legend: {
data: ['高危', '预警', '普通'],
bottom: 0
},
{
name: '普通',
type: 'line',
data: [250, 280, 270, 290, 285, 275, 280],
itemStyle: { color: '#666' },
lineStyle: { color: '#666' },
symbol: 'circle',
symbolSize: 6
}
]
}))
grid: {
left: '3%',
right: '4%',
bottom: '15%',
containLabel: true
},
xAxis: {
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,
total: 3,
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: '三级风险'
}
]
total: 0,
results: []
})
//
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) => {
pageSize.value = val
tableData.value.current = 1 //
tableData.value.current = 1
loadFocusPersonPage()
}
//
const handleCurrentChange = (val: number) => {
tableData.value.current = val
loadFocusPersonPage()
}
const handleView = (row: any) => {
const handleView = (row: FocusPersonVO) => {
router.push({
name: 'Dashboard',
path: '/prisoner/prisoner/dashboard',
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>
<style scoped lang="scss">

View File

@ -67,6 +67,14 @@
>
<Icon icon="ep:plus" class="mr-5px" /> 新增
</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
type="success"
plain
@ -166,6 +174,16 @@
<!-- 明细查看弹窗 -->
<ConsumptionDetailDialog ref="detailDialogRef" />
<!-- 导入弹窗 -->
<ImportDialog
ref="importDialogRef"
:import-url="getImportUrl()"
template-url="/prison/consumption/get-import-template"
template-name="消费记录导入模板.xls"
title="导入消费记录"
@success="getList"
/>
</template>
<script lang="ts" setup>
@ -175,6 +193,7 @@ import download from '@/utils/download'
import { ConsumptionApi, Consumption } from '@/api/prison/consumption'
import ConsumptionForm from './ConsumptionForm.vue'
import ConsumptionDetailDialog from './ConsumptionDetailDialog.vue'
import ImportDialog from '@/components/ImportDialog/index.vue'
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(() => {
getList()

View File

@ -76,6 +76,14 @@
>
<Icon icon="ep:plus" class="mr-5px" /> 新增评估
</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
type="success"
plain
@ -172,6 +180,16 @@
<!-- 表单弹窗添加/修改 -->
<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>
<script lang="ts" setup>
@ -180,6 +198,7 @@ import { formatDateTime, formatDate } from '@/utils/formatTime'
import download from '@/utils/download'
import { RiskApi, RiskPageReqVO } from '@/api/prison/risk'
import RiskForm from './RiskForm.vue'
import ImportDialog from '@/components/ImportDialog/index.vue'
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(() => {
getList()

View File

@ -82,6 +82,14 @@
>
<Icon icon="ep:plus" class="mr-5px" /> 新增
</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
type="success"
plain
@ -178,6 +186,16 @@
<!-- 表单弹窗添加/修改 -->
<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>
<script lang="ts" setup>
@ -186,6 +204,7 @@ import { formatDateTime } from '@/utils/formatTime'
import download from '@/utils/download'
import { RiskAssessmentApi, RiskAssessment } from '@/api/prison/riskassessment'
import RiskAssessmentForm from './RiskAssessmentForm.vue'
import ImportDialog from '@/components/ImportDialog/index.vue'
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(() => {
getList()

View File

@ -76,6 +76,14 @@
>
<Icon icon="ep:plus" class="mr-5px" /> 新增
</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
type="success"
plain
@ -164,6 +172,16 @@
<!-- 表单弹窗添加/修改 -->
<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>
<script lang="ts" setup>
@ -172,6 +190,7 @@ import { formatDateTime } from '@/utils/formatTime'
import download from '@/utils/download'
import { ScoreApi, Score } from '@/api/prison/score'
import ScoreForm from './ScoreForm.vue'
import ImportDialog from '@/components/ImportDialog/index.vue'
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(() => {
getList()

View File

@ -85,6 +85,14 @@
>
<Icon icon="ep:plus" class="mr-5px" /> 新增狱情
</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
type="success"
plain
@ -184,6 +192,16 @@
<!-- 表单弹窗添加/修改 -->
<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>
<script lang="ts" setup>
@ -192,6 +210,7 @@ import { formatDateTime } from '@/utils/formatTime'
import download from '@/utils/download'
import { SituationApi, SituationPageReqVO } from '@/api/prison/situation'
import SituationForm from './SituationForm.vue'
import ImportDialog from '@/components/ImportDialog/index.vue'
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(() => {
getList()

View File

@ -100,6 +100,14 @@
>
<Icon icon="ep:plus" class="mr-5px" /> 新增预警
</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
type="success"
plain
@ -201,6 +209,16 @@
<!-- 预警处置操作弹窗 -->
<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>
<script lang="ts" setup>
@ -210,6 +228,7 @@ import download from '@/utils/download'
import { WarningApi, WarningPageReqVO } from '@/api/prison/warning'
import WarningForm from './WarningForm.vue'
import WarningActionForm from './WarningActionForm.vue'
import ImportDialog from '@/components/ImportDialog/index.vue'
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(() => {
getList()