冲突解决
This commit is contained in:
commit
2cc61a524d
2
.env
2
.env
@ -1,5 +1,5 @@
|
|||||||
# 标题
|
# 标题
|
||||||
VITE_APP_TITLE=芋道管理系统
|
VITE_APP_TITLE=AI心航360°
|
||||||
|
|
||||||
# 项目本地运行端口号
|
# 项目本地运行端口号
|
||||||
VITE_PORT=80
|
VITE_PORT=80
|
||||||
|
|||||||
71
src/api/prison/ai-dash-entry/index.ts
Normal file
71
src/api/prison/ai-dash-entry/index.ts
Normal 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 })
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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 })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import request from '@/config/axios'
|
import request from '@/config/axios'
|
||||||
|
import { getAccessToken } from '@/utils/auth'
|
||||||
|
|
||||||
export interface TemplateVO {
|
export interface TemplateVO {
|
||||||
id?: number
|
id?: number
|
||||||
@ -72,10 +73,15 @@ export interface ReportVO {
|
|||||||
templateName?: string
|
templateName?: string
|
||||||
title?: string
|
title?: string
|
||||||
evaluationDate?: string
|
evaluationDate?: string
|
||||||
|
evaluationType?: number
|
||||||
|
evaluationCycle?: number
|
||||||
riskLevel?: number
|
riskLevel?: number
|
||||||
conclusion?: string
|
conclusion?: string
|
||||||
suggestions?: string // 与后端一致
|
suggestions?: string // 与后端一致
|
||||||
areaName?: string // 与后端一致
|
areaName?: string // 与后端一致
|
||||||
|
areaId?: number
|
||||||
|
evaluatorId?: number
|
||||||
|
evaluatorName?: string
|
||||||
status: number
|
status: number
|
||||||
aiStatus?: number
|
aiStatus?: number
|
||||||
auditorId?: number
|
auditorId?: number
|
||||||
@ -86,6 +92,17 @@ export interface ReportVO {
|
|||||||
createTime?: string
|
createTime?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 报告更新请求VO - 只包含需要更新的字段
|
||||||
|
export interface ReportUpdateReqVO {
|
||||||
|
id: number
|
||||||
|
dimensions?: string // 维度内容,JSON格式
|
||||||
|
conclusion?: string
|
||||||
|
suggestions?: string
|
||||||
|
riskLevel?: number
|
||||||
|
status?: number
|
||||||
|
remark?: string
|
||||||
|
}
|
||||||
|
|
||||||
export interface ReportPageReqVO {
|
export interface ReportPageReqVO {
|
||||||
pageNo: number
|
pageNo: number
|
||||||
pageSize: number
|
pageSize: number
|
||||||
@ -109,6 +126,7 @@ export interface DimensionDataVO {
|
|||||||
modifiedTime?: string
|
modifiedTime?: string
|
||||||
dataSource?: string
|
dataSource?: string
|
||||||
rawData?: string
|
rawData?: string
|
||||||
|
createTime?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CommentVO {
|
export interface CommentVO {
|
||||||
@ -318,7 +336,8 @@ export interface DimensionDataSourcesVO {
|
|||||||
// ========== 流式生成 API ==========
|
// ========== 流式生成 API ==========
|
||||||
export const StreamApi = {
|
export const StreamApi = {
|
||||||
// 流式生成维度内容
|
// 流式生成维度内容
|
||||||
streamGenerateDimension: async (dimensionId: number, prisonerId: number, customPrompt?: string): Promise<EventSource> => {
|
// 注意:EventSource API 不支持自定义 HTTP Header,所以需要通过 URL 参数传递 token
|
||||||
|
streamGenerateDimension: async (dimensionId: number, prisonerId: number, customPrompt?: string, systemPrompt?: string): Promise<EventSource> => {
|
||||||
const baseUrl = import.meta.env.VITE_BASE_URL || ''
|
const baseUrl = import.meta.env.VITE_BASE_URL || ''
|
||||||
const url = new URL(`${baseUrl}/admin-api/prison/evaluation-report/dimension/stream-generate`, window.location.origin)
|
const url = new URL(`${baseUrl}/admin-api/prison/evaluation-report/dimension/stream-generate`, window.location.origin)
|
||||||
url.searchParams.set('dimensionId', dimensionId.toString())
|
url.searchParams.set('dimensionId', dimensionId.toString())
|
||||||
@ -326,6 +345,14 @@ export const StreamApi = {
|
|||||||
if (customPrompt) {
|
if (customPrompt) {
|
||||||
url.searchParams.set('customPrompt', customPrompt)
|
url.searchParams.set('customPrompt', customPrompt)
|
||||||
}
|
}
|
||||||
|
if (systemPrompt) {
|
||||||
|
url.searchParams.set('systemPrompt', systemPrompt)
|
||||||
|
}
|
||||||
|
// 添加 token 参数用于认证(EventSource 不支持 Header)
|
||||||
|
const token = getAccessToken()
|
||||||
|
if (token) {
|
||||||
|
url.searchParams.set('token', token)
|
||||||
|
}
|
||||||
return new EventSource(url.toString())
|
return new EventSource(url.toString())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -108,12 +108,17 @@ export interface EvaluationReport {
|
|||||||
/** 快捷评语 */
|
/** 快捷评语 */
|
||||||
export interface ReportComment {
|
export interface ReportComment {
|
||||||
id: number
|
id: number
|
||||||
|
commentType: number // 评语类型:1-入监评估 2-定期考核 3-出监评估 4-减刑假释 5-专项评估
|
||||||
|
dimensionId?: number // 维度ID
|
||||||
|
dimensionName?: string // 维度名称
|
||||||
content: string // 评语内容
|
content: string // 评语内容
|
||||||
type: number // 评语类型:1-入监评估 2-定期考核 3-出监评估 4-减刑假释 5-专项评估
|
level?: number // 评级等级:1-优秀 2-良好 3-一般 4-较差 5-危险
|
||||||
dimension?: string // 适用维度
|
tags?: string // 标签(逗号分隔)
|
||||||
usageCount?: number // 使用次数
|
useCount?: number // 使用次数
|
||||||
isBuiltin?: boolean // 是否内置
|
isBuiltin?: boolean // 是否内置
|
||||||
|
sort?: number // 排序
|
||||||
status: number // 状态:0-停用 1-启用
|
status: number // 状态:0-停用 1-启用
|
||||||
|
remark?: string // 备注
|
||||||
creator?: string
|
creator?: string
|
||||||
createTime?: string
|
createTime?: string
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,8 @@
|
|||||||
import request from '@/config/axios'
|
import request from '@/config/axios'
|
||||||
|
|
||||||
|
// 导入评估报告更新请求类型
|
||||||
|
import type { ReportUpdateReqVO } from '../evaluation-report'
|
||||||
|
|
||||||
// ============ 评估报告模板相关类型 ============
|
// ============ 评估报告模板相关类型 ============
|
||||||
|
|
||||||
/** 评估报告模板分页参数 */
|
/** 评估报告模板分页参数 */
|
||||||
@ -63,6 +66,8 @@ export interface ReportDimensionContent {
|
|||||||
aiGenerateTime?: string // AI生成时间
|
aiGenerateTime?: string // AI生成时间
|
||||||
lastModifyTime?: string // 最后修改时间
|
lastModifyTime?: string // 最后修改时间
|
||||||
lastModifyBy?: string // 最后修改人
|
lastModifyBy?: string // 最后修改人
|
||||||
|
enableAi?: boolean
|
||||||
|
sort?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 评估报告 */
|
/** 评估报告 */
|
||||||
@ -72,6 +77,8 @@ export interface Report {
|
|||||||
prisonerId: number // 罪犯ID
|
prisonerId: number // 罪犯ID
|
||||||
prisonerNo: string // 罪犯编号
|
prisonerNo: string // 罪犯编号
|
||||||
prisonerName?: string // 罪犯姓名
|
prisonerName?: string // 罪犯姓名
|
||||||
|
areaId?: number // 监区ID
|
||||||
|
areaName?: string // 监区名称
|
||||||
templateId: number // 模板ID
|
templateId: number // 模板ID
|
||||||
templateName?: string // 模板名称
|
templateName?: string // 模板名称
|
||||||
title: string // 报告标题
|
title: string // 报告标题
|
||||||
@ -97,6 +104,23 @@ export interface Report {
|
|||||||
createTime?: string
|
createTime?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 报告创建请求 */
|
||||||
|
export interface ReportCreateReqVO {
|
||||||
|
prisonerId: number
|
||||||
|
templateId: number
|
||||||
|
title: string
|
||||||
|
reportDate?: string
|
||||||
|
evaluationDate?: string
|
||||||
|
evaluationCycle?: number
|
||||||
|
evaluationType?: number
|
||||||
|
prisonerNo?: string
|
||||||
|
prisonerName?: string
|
||||||
|
areaId?: number
|
||||||
|
areaName?: string
|
||||||
|
status?: number
|
||||||
|
dimensions?: ReportDimensionContent[]
|
||||||
|
}
|
||||||
|
|
||||||
// ============ 快捷评语相关类型 ============
|
// ============ 快捷评语相关类型 ============
|
||||||
|
|
||||||
/** 快捷评语分类 */
|
/** 快捷评语分类 */
|
||||||
@ -201,12 +225,12 @@ export const ReportApi = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
// 新增报告
|
// 新增报告
|
||||||
createReport: async (data: Report) => {
|
createReport: async (data: ReportCreateReqVO) => {
|
||||||
return await request.post({ url: '/prison/evaluation-report/report/create', data })
|
return await request.post({ url: '/prison/evaluation-report/report/create', data })
|
||||||
},
|
},
|
||||||
|
|
||||||
// 修改报告
|
// 修改报告 - 只传递需要更新的字段
|
||||||
updateReport: async (data: Report) => {
|
updateReport: async (data: ReportUpdateReqVO) => {
|
||||||
return await request.put({ url: '/prison/evaluation-report/report/update', data })
|
return await request.put({ url: '/prison/evaluation-report/report/update', data })
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@ -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` })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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` })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -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` })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
BIN
src/assets/imgs/avatar.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.5 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 1.5 MiB |
@ -72,7 +72,7 @@ export default defineComponent({
|
|||||||
// 添加标签的文字颜色为白色,解决自定义背景颜色时标签文字看不清的问题
|
// 添加标签的文字颜色为白色,解决自定义背景颜色时标签文字看不清的问题
|
||||||
<ElTag
|
<ElTag
|
||||||
style={dict?.cssClass ? 'color: #fff' : ''}
|
style={dict?.cssClass ? 'color: #fff' : ''}
|
||||||
type={dict?.colorType || null}
|
type={dict?.colorType && dict?.colorType !== 'default' && dict?.colorType !== 'primary' ? dict?.colorType : undefined}
|
||||||
color={dict?.cssClass && isHexColor(dict?.cssClass) ? dict?.cssClass : ''}
|
color={dict?.cssClass && isHexColor(dict?.cssClass) ? dict?.cssClass : ''}
|
||||||
disableTransitions={true}
|
disableTransitions={true}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -171,7 +171,7 @@
|
|||||||
/>
|
/>
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<el-text>手机扫码预览</el-text>
|
<el-text>手机扫码预览</el-text>
|
||||||
<Qrcode :text="previewUrl" logo="/logo.gif" />
|
<Qrcode :text="previewUrl" logo="/logo.png" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|||||||
163
src/components/ImportDialog/index.vue
Normal file
163
src/components/ImportDialog/index.vue
Normal 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>仅允许导入 xls、xlsx 格式文件。</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>
|
||||||
@ -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'
|
||||||
@ -115,7 +119,6 @@ service.interceptors.request.use(
|
|||||||
},
|
},
|
||||||
(error: AxiosError) => {
|
(error: AxiosError) => {
|
||||||
// Do something with request error
|
// Do something with request error
|
||||||
console.log(error) // for debug
|
|
||||||
return Promise.reject(error)
|
return Promise.reject(error)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -235,7 +238,6 @@ service.interceptors.response.use(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
(error: AxiosError) => {
|
(error: AxiosError) => {
|
||||||
console.log('err' + error) // for debug
|
|
||||||
let { message } = error
|
let { message } = error
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
if (message === 'Network Error') {
|
if (message === 'Network Error') {
|
||||||
|
|||||||
@ -68,10 +68,10 @@ const toDocument = () => {
|
|||||||
<Icon icon="ep:tools" />
|
<Icon icon="ep:tools" />
|
||||||
<div @click="toProfile">{{ t('common.profile') }}</div>
|
<div @click="toProfile">{{ t('common.profile') }}</div>
|
||||||
</ElDropdownItem>
|
</ElDropdownItem>
|
||||||
<ElDropdownItem>
|
<!-- <ElDropdownItem>
|
||||||
<Icon icon="ep:menu" />
|
<Icon icon="ep:menu" />
|
||||||
<div @click="toDocument">{{ t('common.document') }}</div>
|
<div @click="toDocument">{{ t('common.document') }}</div>
|
||||||
</ElDropdownItem>
|
</ElDropdownItem> -->
|
||||||
<ElDropdownItem divided>
|
<ElDropdownItem divided>
|
||||||
<Icon icon="ep:lock" />
|
<Icon icon="ep:lock" />
|
||||||
<div @click="lockScreen">{{ t('lock.lockScreen') }}</div>
|
<div @click="lockScreen">{{ t('lock.lockScreen') }}</div>
|
||||||
|
|||||||
@ -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',
|
||||||
|
|||||||
@ -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 报错
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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}`) // 否则全部重定向到登录页
|
||||||
|
|||||||
@ -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'),
|
||||||
@ -769,158 +759,8 @@ const remainingRouter: AppRouteRecordRaw[] = [
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
// 监管看板路由(开发测试用,上线后由后端菜单动态生成)
|
|
||||||
{
|
|
||||||
path: '/prison',
|
|
||||||
component: Layout,
|
|
||||||
name: 'Prison',
|
|
||||||
meta: {
|
|
||||||
hidden: true
|
|
||||||
},
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
path: 'prisoner/dashboard',
|
|
||||||
component: () => import('@/views/Dashboard/Index.vue'),
|
|
||||||
name: 'PrisonerDashboardInPrison',
|
|
||||||
meta: {
|
|
||||||
title: '个人中心',
|
|
||||||
icon: 'ep:user',
|
|
||||||
permission: 'prison:prisoner:dashboard',
|
|
||||||
noCache: false,
|
|
||||||
hidden: true,
|
|
||||||
canTo: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'situation-platform',
|
|
||||||
component: () => import('@/views/prison/situation/index.vue'),
|
|
||||||
name: 'PrisonSituationPlatform',
|
|
||||||
meta: {
|
|
||||||
title: '狱情收集',
|
|
||||||
icon: 'ep:warning',
|
|
||||||
permission: 'prison:situation:query',
|
|
||||||
noCache: false,
|
|
||||||
hidden: true,
|
|
||||||
canTo: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'report/edit',
|
|
||||||
component: () => import('@/views/prison/evaluation-mgmt/report/ReportForm.vue'),
|
|
||||||
name: 'PrisonReportEdit',
|
|
||||||
meta: {
|
|
||||||
title: '评估报告编辑',
|
|
||||||
icon: 'ep:document-checked',
|
|
||||||
permission: 'prison:report:update',
|
|
||||||
noCache: true,
|
|
||||||
hidden: true,
|
|
||||||
canTo: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
// 评估报告管理路由(开发测试用)
|
|
||||||
{
|
|
||||||
path: '/prison/evaluation-mgmt',
|
|
||||||
component: Layout,
|
|
||||||
redirect: '/prison/evaluation-mgmt/template',
|
|
||||||
name: 'PrisonEvaluationMgmt',
|
|
||||||
meta: {
|
|
||||||
title: '评估报告管理',
|
|
||||||
icon: 'documentation',
|
|
||||||
hidden: true
|
|
||||||
},
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
path: 'template',
|
|
||||||
component: () => import('@/views/prison/evaluation-mgmt/template/index.vue'),
|
|
||||||
name: 'EvaluationTemplate',
|
|
||||||
meta: {
|
|
||||||
title: '模板管理',
|
|
||||||
icon: 'component',
|
|
||||||
permission: 'prison:evaluation-report:template:query',
|
|
||||||
noCache: false,
|
|
||||||
hidden: false,
|
|
||||||
canTo: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'report',
|
|
||||||
component: () => import('@/views/prison/evaluation-mgmt/report/index.vue'),
|
|
||||||
name: 'EvaluationReport',
|
|
||||||
meta: {
|
|
||||||
title: '报告管理',
|
|
||||||
icon: 'documentation',
|
|
||||||
permission: 'prison:evaluation-report:report:query',
|
|
||||||
noCache: false,
|
|
||||||
hidden: false,
|
|
||||||
canTo: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'report/edit',
|
|
||||||
component: () => import('@/views/prison/evaluation-mgmt/report/ReportForm.vue'),
|
|
||||||
name: 'EvaluationReportEdit',
|
|
||||||
meta: {
|
|
||||||
title: '评估报告编辑',
|
|
||||||
icon: 'document-checked',
|
|
||||||
permission: 'prison:evaluation-report:report:update',
|
|
||||||
noCache: true,
|
|
||||||
hidden: true,
|
|
||||||
canTo: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'comment',
|
|
||||||
component: () => import('@/views/prison/evaluation-mgmt/comment/index.vue'),
|
|
||||||
name: 'EvaluationComment',
|
|
||||||
meta: {
|
|
||||||
title: '评语管理',
|
|
||||||
icon: 'chat-dot-round',
|
|
||||||
permission: 'prison:evaluation-report:comment:query',
|
|
||||||
noCache: false,
|
|
||||||
hidden: false,
|
|
||||||
canTo: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'report-template',
|
|
||||||
redirect: '/prison/evaluation-mgmt/template',
|
|
||||||
name: 'ReportTemplateRedirect',
|
|
||||||
meta: {
|
|
||||||
title: '报告模板',
|
|
||||||
hidden: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
// 服刑人员评估报告管理(新版)
|
|
||||||
{
|
|
||||||
path: '/prison/evaluation-mgmt',
|
|
||||||
component: Layout,
|
|
||||||
redirect: '/prison/evaluation-mgmt/prisoner-manage',
|
|
||||||
name: 'PrisonEvaluationMgmt',
|
|
||||||
meta: {
|
|
||||||
title: '评估报告管理',
|
|
||||||
icon: 'documentation',
|
|
||||||
hidden: false
|
|
||||||
},
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
path: 'prisoner-manage',
|
|
||||||
component: () => import('@/views/prison/evaluation-report/prisoner/ReportManage.vue'),
|
|
||||||
name: 'PrisonerReportManage',
|
|
||||||
meta: {
|
|
||||||
title: '服刑人员报告管理',
|
|
||||||
icon: 'user',
|
|
||||||
permission: 'prison:evaluation-report:prisoner:query',
|
|
||||||
noCache: false,
|
|
||||||
hidden: false,
|
|
||||||
canTo: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
|
|
||||||
export default remainingRouter
|
export default remainingRouter
|
||||||
|
|||||||
@ -72,34 +72,34 @@ export const useAppStore = defineStore('app', {
|
|||||||
isDark: wsCache.get(CACHE_KEY.IS_DARK) || false, // 是否是暗黑模式
|
isDark: wsCache.get(CACHE_KEY.IS_DARK) || false, // 是否是暗黑模式
|
||||||
currentSize: wsCache.get('default') || 'default', // 组件尺寸
|
currentSize: wsCache.get('default') || 'default', // 组件尺寸
|
||||||
theme: wsCache.get(CACHE_KEY.THEME) || {
|
theme: wsCache.get(CACHE_KEY.THEME) || {
|
||||||
// 主题色
|
// 主题色
|
||||||
elColorPrimary: '#409eff',
|
elColorPrimary: '#536dfe',
|
||||||
// 左侧菜单边框颜色
|
// 左侧菜单边框颜色
|
||||||
leftMenuBorderColor: 'inherit',
|
leftMenuBorderColor: '#eee',
|
||||||
// 左侧菜单背景颜色
|
// 左侧菜单背景颜色
|
||||||
leftMenuBgColor: '#001529',
|
leftMenuBgColor: '#fff',
|
||||||
// 左侧菜单浅色背景颜色
|
// 左侧菜单浅色背景颜色
|
||||||
leftMenuBgLightColor: '#0f2438',
|
leftMenuBgLightColor: '#fff',
|
||||||
// 左侧菜单选中背景颜色
|
// 左侧菜单选中背景颜色
|
||||||
leftMenuBgActiveColor: 'var(--el-color-primary)',
|
leftMenuBgActiveColor: 'RGBA(83,109,254,0.1)',
|
||||||
// 左侧菜单收起选中背景颜色
|
// 左侧菜单收起选中背景颜色
|
||||||
leftMenuCollapseBgActiveColor: 'var(--el-color-primary)',
|
leftMenuCollapseBgActiveColor: 'RGBA(83,109,254,0.1)',
|
||||||
// 左侧菜单字体颜色
|
// 左侧菜单字体颜色
|
||||||
leftMenuTextColor: '#bfcbd9',
|
leftMenuTextColor: '#333',
|
||||||
// 左侧菜单选中字体颜色
|
// 左侧菜单选中字体颜色
|
||||||
leftMenuTextActiveColor: '#fff',
|
leftMenuTextActiveColor: 'var(--el-color-primary)',
|
||||||
// logo字体颜色
|
// logo字体颜色
|
||||||
logoTitleTextColor: '#fff',
|
logoTitleTextColor: 'inherit',
|
||||||
// logo边框颜色
|
// logo边框颜色
|
||||||
logoBorderColor: 'inherit',
|
logoBorderColor: '#eee',
|
||||||
// 头部背景颜色
|
// 头部背景颜色
|
||||||
topHeaderBgColor: '#fff',
|
topHeaderBgColor: '#fff',
|
||||||
// 头部字体颜色
|
// 头部字体颜色
|
||||||
topHeaderTextColor: 'inherit',
|
topHeaderTextColor: 'inherit',
|
||||||
// 头部悬停颜色
|
// 头部悬停颜色
|
||||||
topHeaderHoverColor: '#f6f6f6',
|
topHeaderHoverColor: '#f6f6f6',
|
||||||
// 头部边框颜色
|
// 头部边框颜色
|
||||||
topToolBorderColor: '#eee'
|
topToolBorderColor: '#eee'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@ -270,6 +270,10 @@ export enum DICT_TYPE {
|
|||||||
PRISON_RECORD_PASS_STATUS = 'prison_record_pass_status', // 问卷答题是否及格
|
PRISON_RECORD_PASS_STATUS = 'prison_record_pass_status', // 问卷答题是否及格
|
||||||
PRISON_RECORD_STATUS = 'prison_record_status', // 问卷答题记录状态
|
PRISON_RECORD_STATUS = 'prison_record_status', // 问卷答题记录状态
|
||||||
PRISON_QUESTION_AUTO_FILL_SOURCE = 'prison_question_auto_fill_source', // 问卷问题自动填充来源
|
PRISON_QUESTION_AUTO_FILL_SOURCE = 'prison_question_auto_fill_source', // 问卷问题自动填充来源
|
||||||
|
PRISON_SITUATION_CATEGORY = 'prison_situation_category', // 狱情分类
|
||||||
|
PRISON_SITUATION_LEVEL = 'prison_situation_level', // 狱情等级
|
||||||
|
PRISON_SITUATION_SOURCE = 'prison_situation_source', // 狱情来源
|
||||||
|
PRISON_SITUATION_STATUS = 'prison_situation_status', // 狱情状态
|
||||||
PRISON_AREA_LEVEL = 'prison_area_level', // 监区级别:1-监区(大队) 2-分监区(中队)
|
PRISON_AREA_LEVEL = 'prison_area_level', // 监区级别:1-监区(大队) 2-分监区(中队)
|
||||||
PRISON_RELEASE_TYPE = 'prison_release_type', // 释放类型:1-刑满释放 2-假释 3-暂予监外执行 4-减刑 5-法院裁定释放 6-死亡 7-其他
|
PRISON_RELEASE_TYPE = 'prison_release_type', // 释放类型:1-刑满释放 2-假释 3-暂予监外执行 4-减刑 5-法院裁定释放 6-死亡 7-其他
|
||||||
PRISON_AREA_CHANGE_TYPE = 'prison_area_change_type', // 变动类型:1-入监分配 2-调监 3-出监 4-移交转入 5-移交转出
|
PRISON_AREA_CHANGE_TYPE = 'prison_area_change_type', // 变动类型:1-入监分配 2-调监 3-出监 4-移交转入 5-移交转出
|
||||||
@ -281,6 +285,17 @@ export enum DICT_TYPE {
|
|||||||
PRISON_ASSESSMENT_PASS_STATUS = 'prison_assessment_pass_status', // 测评及格状态:1-及格 2-不及格 3-待评分
|
PRISON_ASSESSMENT_PASS_STATUS = 'prison_assessment_pass_status', // 测评及格状态:1-及格 2-不及格 3-待评分
|
||||||
PRISON_ASSESSMENT_ANSWER_STATUS = 'prison_assessment_answer_status', // 测评答题状态:1-待评分 2-已评分
|
PRISON_ASSESSMENT_ANSWER_STATUS = 'prison_assessment_answer_status', // 测评答题状态:1-待评分 2-已评分
|
||||||
|
|
||||||
|
// ========== 预警模块 ==========
|
||||||
|
PRISON_WARNING_TYPE = 'prison_warning_type', // 预警类型
|
||||||
|
PRISON_WARNING_LEVEL = 'prison_warning_level', // 预警等级
|
||||||
|
PRISON_WARNING_SOURCE = 'prison_warning_source', // 预警来源
|
||||||
|
PRISON_WARNING_STATUS = 'prison_warning_status', // 预警状态
|
||||||
|
|
||||||
|
// ========== 风险评估模块 ==========
|
||||||
|
PRISON_RISK_ASSESSMENT_TYPE = 'prison_risk_assessment_type', // 风险评估类型:1-入监评估 2-定期评估 3-专项评估 4-出监评估
|
||||||
|
PRISON_RISK_ASSESS_METHOD = 'prison_risk_assess_method', // 评估方式:1-问卷评估 2-量表评估 3-综合评估
|
||||||
|
PRISON_RISK_MENTAL_STATE = 'prison_risk_mental_state', // 精神状态:1-正常 2-异常
|
||||||
|
|
||||||
// ========== 评估报告模块 ==========
|
// ========== 评估报告模块 ==========
|
||||||
PRISON_REPORT_STATUS = 'prison_report_status', // 报告状态:1-草稿 2-待审核 3-已通过 4-已退回 5-已归档
|
PRISON_REPORT_STATUS = 'prison_report_status', // 报告状态:1-草稿 2-待审核 3-已通过 4-已退回 5-已归档
|
||||||
PRISON_REPORT_TEMPLATE_TYPE = 'prison_report_template_type', // 报告模板类型:1-心理评估 2-危险性评估 3-改造表现评估 4-综合评估
|
PRISON_REPORT_TEMPLATE_TYPE = 'prison_report_template_type', // 报告模板类型:1-心理评估 2-危险性评估 3-改造表现评估 4-综合评估
|
||||||
|
|||||||
@ -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">
|
||||||
|
|||||||
@ -9,7 +9,7 @@
|
|||||||
>
|
>
|
||||||
<!-- 左上角的 logo + 系统标题 -->
|
<!-- 左上角的 logo + 系统标题 -->
|
||||||
<div class="relative flex items-center text-white">
|
<div class="relative flex items-center text-white">
|
||||||
<img alt="" class="mr-10px h-48px w-48px" src="@/assets/imgs/logo.png" />
|
<img alt="" class="mr-10px h-48px w-48px rounded-[22px]" src="@/assets/imgs/logo.png" />
|
||||||
<span class="text-20px font-bold">{{ underlineToHump(appStore.getTitle) }}</span>
|
<span class="text-20px font-bold">{{ underlineToHump(appStore.getTitle) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<!-- 左边的背景图 + 欢迎语 -->
|
<!-- 左边的背景图 + 欢迎语 -->
|
||||||
@ -18,10 +18,11 @@
|
|||||||
appear
|
appear
|
||||||
enter-active-class="animate__animated animate__bounceInLeft"
|
enter-active-class="animate__animated animate__bounceInLeft"
|
||||||
tag="div"
|
tag="div"
|
||||||
|
class="flex flex-col items-center"
|
||||||
>
|
>
|
||||||
<img key="1" alt="" class="w-350px" src="@/assets/svgs/login-box-bg.svg" />
|
<img key="1" alt="" class="w-350px" src="@/assets/svgs/login-box-bg.svg" />
|
||||||
<div key="2" class="text-3xl text-white">{{ t('login.welcome') }}</div>
|
<div key="2" class="text-3xl text-white">{{ t('login.welcome') }}</div>
|
||||||
<div key="3" class="mt-5 text-14px font-normal text-white">
|
<div key="3" class="mt-5 text-14px font-normal text-white login-message">
|
||||||
{{ t('login.message') }}
|
{{ t('login.message') }}
|
||||||
</div>
|
</div>
|
||||||
</TransitionGroup>
|
</TransitionGroup>
|
||||||
@ -51,16 +52,6 @@
|
|||||||
>
|
>
|
||||||
<!-- 账号登录 -->
|
<!-- 账号登录 -->
|
||||||
<LoginForm class="m-auto h-auto p-20px lt-xl:(rounded-3xl light:bg-white)" />
|
<LoginForm class="m-auto h-auto p-20px lt-xl:(rounded-3xl light:bg-white)" />
|
||||||
<!-- 手机登录 -->
|
|
||||||
<MobileForm class="m-auto h-auto p-20px lt-xl:(rounded-3xl light:bg-white)" />
|
|
||||||
<!-- 二维码登录 -->
|
|
||||||
<QrCodeForm class="m-auto h-auto p-20px lt-xl:(rounded-3xl light:bg-white)" />
|
|
||||||
<!-- 注册 -->
|
|
||||||
<RegisterForm class="m-auto h-auto p-20px lt-xl:(rounded-3xl light:bg-white)" />
|
|
||||||
<!-- 三方登录 -->
|
|
||||||
<SSOLoginVue class="m-auto h-auto p-20px lt-xl:(rounded-3xl light:bg-white)" />
|
|
||||||
<!-- 忘记密码 -->
|
|
||||||
<ForgetPasswordForm class="m-auto h-auto p-20px lt-xl:(rounded-3xl light:bg-white)" />
|
|
||||||
</div>
|
</div>
|
||||||
</Transition>
|
</Transition>
|
||||||
</div>
|
</div>
|
||||||
@ -75,7 +66,7 @@ import { useAppStore } from '@/store/modules/app'
|
|||||||
import { ThemeSwitch } from '@/layout/components/ThemeSwitch'
|
import { ThemeSwitch } from '@/layout/components/ThemeSwitch'
|
||||||
import { LocaleDropdown } from '@/layout/components/LocaleDropdown'
|
import { LocaleDropdown } from '@/layout/components/LocaleDropdown'
|
||||||
|
|
||||||
import { LoginForm, MobileForm, QrCodeForm, RegisterForm, SSOLoginVue, ForgetPasswordForm } from './components'
|
import { LoginForm } from './components'
|
||||||
|
|
||||||
defineOptions({ name: 'Login' })
|
defineOptions({ name: 'Login' })
|
||||||
|
|
||||||
@ -106,6 +97,11 @@ $prefix-cls: #{$namespace}-login;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.login-message {
|
||||||
|
text-align: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|||||||
@ -25,7 +25,8 @@
|
|||||||
<LoginFormTitle class="w-full" />
|
<LoginFormTitle class="w-full" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="24" class="px-10px">
|
<!-- 租户选择已隐藏,默认使用租户1 -->
|
||||||
|
<!-- <el-col :span="24" class="px-10px">
|
||||||
<el-form-item v-if="loginData.tenantEnable === 'true'" prop="tenantName">
|
<el-form-item v-if="loginData.tenantEnable === 'true'" prop="tenantName">
|
||||||
<el-input
|
<el-input
|
||||||
v-model="loginData.loginForm.tenantName"
|
v-model="loginData.loginForm.tenantName"
|
||||||
@ -35,13 +36,14 @@
|
|||||||
type="primary"
|
type="primary"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col> -->
|
||||||
<el-col :span="24" class="px-10px">
|
<el-col :span="24" class="px-10px">
|
||||||
<el-form-item prop="username">
|
<el-form-item prop="username">
|
||||||
<el-input
|
<el-input
|
||||||
v-model="loginData.loginForm.username"
|
v-model="loginData.loginForm.username"
|
||||||
:placeholder="t('login.usernamePlaceholder')"
|
:placeholder="t('login.usernamePlaceholder')"
|
||||||
:prefix-icon="iconAvatar"
|
:prefix-icon="iconAvatar"
|
||||||
|
:autocomplete="autoComplete"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
@ -53,28 +55,16 @@
|
|||||||
:prefix-icon="iconLock"
|
:prefix-icon="iconLock"
|
||||||
show-password
|
show-password
|
||||||
type="password"
|
type="password"
|
||||||
|
:autocomplete="autoComplete"
|
||||||
@keyup.enter="getCode()"
|
@keyup.enter="getCode()"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="24" class="px-10px mt-[-20px] mb-[-20px]">
|
<el-col :span="24" class="px-10px mt-[-20px] mb-[-10px]">
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<el-row justify="space-between" style="width: 100%">
|
<el-checkbox v-model="loginData.loginForm.rememberMe">
|
||||||
<el-col :span="6">
|
{{ t('login.remember') }}
|
||||||
<el-checkbox v-model="loginData.loginForm.rememberMe">
|
</el-checkbox>
|
||||||
{{ t('login.remember') }}
|
|
||||||
</el-checkbox>
|
|
||||||
</el-col>
|
|
||||||
<el-col :offset="6" :span="12">
|
|
||||||
<el-link
|
|
||||||
class="float-right"
|
|
||||||
type="primary"
|
|
||||||
@click="setLoginState(LoginStateEnum.RESET_PASSWORD)"
|
|
||||||
>
|
|
||||||
{{ t('login.forgetPassword') }}
|
|
||||||
</el-link>
|
|
||||||
</el-col>
|
|
||||||
</el-row>
|
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="24" class="px-10px">
|
<el-col :span="24" class="px-10px">
|
||||||
@ -96,7 +86,8 @@
|
|||||||
mode="pop"
|
mode="pop"
|
||||||
@success="handleLogin"
|
@success="handleLogin"
|
||||||
/>
|
/>
|
||||||
<el-col :span="24" class="px-10px">
|
<!-- 底部切换按钮已隐藏 -->
|
||||||
|
<!-- <el-col :span="24" class="px-10px">
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<el-row :gutter="5" justify="space-between" style="width: 100%">
|
<el-row :gutter="5" justify="space-between" style="width: 100%">
|
||||||
<el-col :span="8">
|
<el-col :span="8">
|
||||||
@ -153,7 +144,7 @@
|
|||||||
</el-link>
|
</el-link>
|
||||||
</div>
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col> -->
|
||||||
</el-row>
|
</el-row>
|
||||||
</el-form>
|
</el-form>
|
||||||
</template>
|
</template>
|
||||||
@ -189,6 +180,11 @@ const captchaType = ref('blockPuzzle') // blockPuzzle 滑块 clickWord 点击文
|
|||||||
|
|
||||||
const getShow = computed(() => unref(getLoginState) === LoginStateEnum.LOGIN)
|
const getShow = computed(() => unref(getLoginState) === LoginStateEnum.LOGIN)
|
||||||
|
|
||||||
|
// 自动填充控制:开发环境开启,生产环境关闭
|
||||||
|
const autoComplete = computed(() => {
|
||||||
|
return import.meta.env.DEV ? 'on' : 'off'
|
||||||
|
})
|
||||||
|
|
||||||
const LoginRules = {
|
const LoginRules = {
|
||||||
tenantName: [required],
|
tenantName: [required],
|
||||||
username: [required],
|
username: [required],
|
||||||
@ -197,13 +193,13 @@ const LoginRules = {
|
|||||||
const loginData = reactive({
|
const loginData = reactive({
|
||||||
isShowPassword: false,
|
isShowPassword: false,
|
||||||
captchaEnable: import.meta.env.VITE_APP_CAPTCHA_ENABLE,
|
captchaEnable: import.meta.env.VITE_APP_CAPTCHA_ENABLE,
|
||||||
tenantEnable: import.meta.env.VITE_APP_TENANT_ENABLE,
|
tenantEnable: 'false', // 默认禁用租户选择
|
||||||
loginForm: {
|
loginForm: {
|
||||||
tenantName: import.meta.env.VITE_APP_DEFAULT_LOGIN_TENANT || '',
|
tenantName: '1', // 默认租户ID为1
|
||||||
username: import.meta.env.VITE_APP_DEFAULT_LOGIN_USERNAME || '',
|
username: import.meta.env.VITE_APP_DEFAULT_LOGIN_USERNAME || '',
|
||||||
password: import.meta.env.VITE_APP_DEFAULT_LOGIN_PASSWORD || '',
|
password: import.meta.env.VITE_APP_DEFAULT_LOGIN_PASSWORD || '',
|
||||||
captchaVerification: '',
|
captchaVerification: '',
|
||||||
rememberMe: true // 默认记录我。如果不需要,可手动修改
|
rememberMe: true // 默认记住密码
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -18,16 +18,16 @@
|
|||||||
<el-tab-pane :label="t('profile.info.resetPwd')" name="resetPwd">
|
<el-tab-pane :label="t('profile.info.resetPwd')" name="resetPwd">
|
||||||
<ResetPwd />
|
<ResetPwd />
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
<el-tab-pane :label="t('profile.info.userSocial')" name="userSocial">
|
<!-- <el-tab-pane :label="t('profile.info.userSocial')" name="userSocial">
|
||||||
<UserSocial v-model:activeName="activeName" />
|
<UserSocial v-model:activeName="activeName" />
|
||||||
</el-tab-pane>
|
</el-tab-pane> -->
|
||||||
</el-tabs>
|
</el-tabs>
|
||||||
</div>
|
</div>
|
||||||
</el-card>
|
</el-card>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { BasicInfo, ProfileUser, ResetPwd, UserSocial } from './components'
|
import { BasicInfo, ProfileUser, ResetPwd } from './components'
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
defineOptions({ name: 'Profile' })
|
defineOptions({ name: 'Profile' })
|
||||||
|
|||||||
@ -169,12 +169,10 @@ const DIY_PAGE_INDEX_KEY = 'diy_page_index'
|
|||||||
|
|
||||||
// 1. 记录
|
// 1. 记录
|
||||||
function storePageIndex() {
|
function storePageIndex() {
|
||||||
debugger
|
|
||||||
return sessionStorage.setItem(DIY_PAGE_INDEX_KEY, `${selectedTemplateItem.value}`)
|
return sessionStorage.setItem(DIY_PAGE_INDEX_KEY, `${selectedTemplateItem.value}`)
|
||||||
}
|
}
|
||||||
// 2. 恢复
|
// 2. 恢复
|
||||||
const recoverPageIndex = () => {
|
const recoverPageIndex = () => {
|
||||||
debugger
|
|
||||||
// 恢复重置前的页面,默认是第一个页面
|
// 恢复重置前的页面,默认是第一个页面
|
||||||
const pageIndex = toNumber(sessionStorage.getItem(DIY_PAGE_INDEX_KEY)) || 0
|
const pageIndex = toNumber(sessionStorage.getItem(DIY_PAGE_INDEX_KEY)) || 0
|
||||||
// 移除标记
|
// 移除标记
|
||||||
|
|||||||
@ -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()
|
||||||
|
|||||||
@ -2,15 +2,15 @@
|
|||||||
<el-dialog v-model="dialogVisible" :title="isCreate ? '新增快捷评语' : '编辑快捷评语'" width="600px" :close-on-click-modal="false">
|
<el-dialog v-model="dialogVisible" :title="isCreate ? '新增快捷评语' : '编辑快捷评语'" width="600px" :close-on-click-modal="false">
|
||||||
<el-form ref="formRef" :model="formData" :rules="rules" label-width="100px">
|
<el-form ref="formRef" :model="formData" :rules="rules" label-width="100px">
|
||||||
<el-form-item label="评语内容" prop="content">
|
<el-form-item label="评语内容" prop="content">
|
||||||
<el-input v-model="formData.content" type="textarea" rows="4" placeholder="请输入评语内容" />
|
<el-input v-model="formData.content" type="textarea" :rows="4" placeholder="请输入评语内容" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="评语类型" prop="type">
|
<el-form-item label="评语类型" prop="commentType">
|
||||||
<el-select v-model="formData.type" placeholder="请选择评语类型" class="!w-full">
|
<el-select v-model="formData.commentType" placeholder="请选择评语类型" class="!w-full">
|
||||||
<el-option v-for="dict in getIntDictOptions(DICT_TYPE.PRISON_REPORT_COMMENT_TYPE)" :key="dict.value" :label="dict.label" :value="dict.value" />
|
<el-option v-for="dict in getIntDictOptions(DICT_TYPE.PRISON_REPORT_COMMENT_TYPE)" :key="dict.value" :label="dict.label" :value="dict.value" />
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="适用维度" prop="dimension">
|
<el-form-item label="维度名称" prop="dimensionName">
|
||||||
<el-input v-model="formData.dimension" placeholder="请输入适用维度,如:服刑表现、心理状态" />
|
<el-input v-model="formData.dimensionName" placeholder="请输入维度名称,如:服刑表现、心理状态" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="状态" prop="status">
|
<el-form-item label="状态" prop="status">
|
||||||
<el-radio-group v-model="formData.status">
|
<el-radio-group v-model="formData.status">
|
||||||
@ -31,6 +31,8 @@ import { ReportCommentApi, ReportComment } from '@/api/prison/evaluation'
|
|||||||
|
|
||||||
defineOptions({ name: 'CommentForm' })
|
defineOptions({ name: 'CommentForm' })
|
||||||
|
|
||||||
|
const message = useMessage()
|
||||||
|
|
||||||
const emit = defineEmits(['success'])
|
const emit = defineEmits(['success'])
|
||||||
|
|
||||||
const dialogVisible = ref(false)
|
const dialogVisible = ref(false)
|
||||||
@ -40,15 +42,20 @@ const formRef = ref()
|
|||||||
|
|
||||||
const formData = reactive({
|
const formData = reactive({
|
||||||
id: undefined,
|
id: undefined,
|
||||||
|
commentType: undefined as number | undefined,
|
||||||
|
dimensionId: undefined as number | undefined,
|
||||||
|
dimensionName: '',
|
||||||
content: '',
|
content: '',
|
||||||
type: undefined as number | undefined,
|
level: undefined as number | undefined,
|
||||||
dimension: '',
|
tags: '',
|
||||||
status: 1
|
sort: 0,
|
||||||
|
status: 1,
|
||||||
|
remark: ''
|
||||||
})
|
})
|
||||||
|
|
||||||
const rules = {
|
const rules = {
|
||||||
content: [{ required: true, message: '评语内容不能为空', trigger: 'blur' }],
|
content: [{ required: true, message: '评语内容不能为空', trigger: 'blur' }],
|
||||||
type: [{ required: true, message: '评语类型不能为空', trigger: 'change' }],
|
commentType: [{ required: true, message: '评语类型不能为空', trigger: 'change' }],
|
||||||
status: [{ required: true, message: '状态不能为空', trigger: 'change' }]
|
status: [{ required: true, message: '状态不能为空', trigger: 'change' }]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,20 +70,30 @@ const open = (type: string, id?: number) => {
|
|||||||
|
|
||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
formData.id = undefined
|
formData.id = undefined
|
||||||
|
formData.commentType = undefined
|
||||||
|
formData.dimensionId = undefined
|
||||||
|
formData.dimensionName = ''
|
||||||
formData.content = ''
|
formData.content = ''
|
||||||
formData.type = undefined
|
formData.level = undefined
|
||||||
formData.dimension = ''
|
formData.tags = ''
|
||||||
|
formData.sort = 0
|
||||||
formData.status = 1
|
formData.status = 1
|
||||||
|
formData.remark = ''
|
||||||
}
|
}
|
||||||
|
|
||||||
const loadData = async (id: number) => {
|
const loadData = async (id: number) => {
|
||||||
const data = await ReportCommentApi.getComment(id)
|
const data = await ReportCommentApi.getComment(id)
|
||||||
if (data) {
|
if (data) {
|
||||||
formData.id = data.id
|
formData.id = data.id
|
||||||
|
formData.commentType = data.commentType
|
||||||
|
formData.dimensionId = data.dimensionId
|
||||||
|
formData.dimensionName = data.dimensionName || ''
|
||||||
formData.content = data.content
|
formData.content = data.content
|
||||||
formData.type = data.type
|
formData.level = data.level
|
||||||
formData.dimension = data.dimension || ''
|
formData.tags = data.tags || ''
|
||||||
|
formData.sort = data.sort || 0
|
||||||
formData.status = data.status
|
formData.status = data.status
|
||||||
|
formData.remark = data.remark || ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -8,9 +8,9 @@
|
|||||||
:inline="true"
|
:inline="true"
|
||||||
label-width="90px"
|
label-width="90px"
|
||||||
>
|
>
|
||||||
<el-form-item label="评语类型" prop="type">
|
<el-form-item label="评语类型" prop="commentType">
|
||||||
<el-select
|
<el-select
|
||||||
v-model="queryParams.type"
|
v-model="queryParams.commentType"
|
||||||
placeholder="请选择"
|
placeholder="请选择"
|
||||||
clearable
|
clearable
|
||||||
class="!w-140px"
|
class="!w-140px"
|
||||||
@ -23,8 +23,8 @@
|
|||||||
/>
|
/>
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="适用维度" prop="dimension">
|
<el-form-item label="适用维度" prop="dimensionName">
|
||||||
<el-input v-model="queryParams.dimension" placeholder="请输入维度" clearable class="!w-140px" />
|
<el-input v-model="queryParams.dimensionName" placeholder="请输入维度" clearable class="!w-140px" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="状态" prop="status">
|
<el-form-item label="状态" prop="status">
|
||||||
<el-select v-model="queryParams.status" placeholder="请选择" clearable class="!w-100px">
|
<el-select v-model="queryParams.status" placeholder="请选择" clearable class="!w-100px">
|
||||||
@ -56,13 +56,13 @@
|
|||||||
<el-table v-loading="loading" :data="list" :stripe="true" @selection-change="handleSelectionChange">
|
<el-table v-loading="loading" :data="list" :stripe="true" @selection-change="handleSelectionChange">
|
||||||
<el-table-column type="selection" width="55" align="center" />
|
<el-table-column type="selection" width="55" align="center" />
|
||||||
<el-table-column label="评语内容" prop="content" min-width="300" show-overflow-tooltip />
|
<el-table-column label="评语内容" prop="content" min-width="300" show-overflow-tooltip />
|
||||||
<el-table-column label="评语类型" prop="type" width="120">
|
<el-table-column label="评语类型" prop="commentType" width="120">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<dict-tag :type="DICT_TYPE.PRISON_REPORT_COMMENT_TYPE" :value="row.type" />
|
<dict-tag :type="DICT_TYPE.PRISON_REPORT_COMMENT_TYPE" :value="row.commentType ?? ''" />
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="适用维度" prop="dimension" width="120" />
|
<el-table-column label="适用维度" prop="dimensionName" width="120" />
|
||||||
<el-table-column label="使用次数" prop="usageCount" width="100" align="center" />
|
<el-table-column label="使用次数" prop="useCount" width="100" align="center" />
|
||||||
<el-table-column label="是否内置" prop="isBuiltin" width="100" align="center">
|
<el-table-column label="是否内置" prop="isBuiltin" width="100" align="center">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-tag v-if="row.isBuiltin" type="info" size="small">是</el-tag>
|
<el-tag v-if="row.isBuiltin" type="info" size="small">是</el-tag>
|
||||||
@ -71,7 +71,7 @@
|
|||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="状态" prop="status" width="100" align="center">
|
<el-table-column label="状态" prop="status" width="100" align="center">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<dict-tag :type="DICT_TYPE.PRISON_COMMON_STATUS" :value="row.status" />
|
<dict-tag :type="DICT_TYPE.PRISON_COMMON_STATUS" :value="row.status ?? ''" />
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="操作" width="150" fixed="right" align="center">
|
<el-table-column label="操作" width="150" fixed="right" align="center">
|
||||||
@ -123,8 +123,8 @@ const ids = ref<number[]>([])
|
|||||||
const queryParams = reactive({
|
const queryParams = reactive({
|
||||||
pageNo: 1,
|
pageNo: 1,
|
||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
type: undefined,
|
commentType: undefined,
|
||||||
dimension: undefined,
|
dimensionName: undefined,
|
||||||
status: undefined
|
status: undefined
|
||||||
})
|
})
|
||||||
const queryFormRef = ref()
|
const queryFormRef = ref()
|
||||||
|
|||||||
11
src/views/prison/evaluation-mgmt/prisoner/index.vue
Normal file
11
src/views/prison/evaluation-mgmt/prisoner/index.vue
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<ContentWrap title="服刑人员报告管理">
|
||||||
|
<ReportManage />
|
||||||
|
</ContentWrap>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import ReportManage from '@/views/prison/evaluation-report/prisoner/ReportManage.vue'
|
||||||
|
</script>
|
||||||
@ -1,60 +1,81 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="dimension-analysis">
|
<div class="dimension-analysis">
|
||||||
<!-- Tab 切换维度 -->
|
<div class="dimension-toolbar">
|
||||||
<div class="dimension-tabs">
|
<el-button
|
||||||
<el-tabs v-model="activeDimensionId" @tab-change="handleDimensionChange">
|
type="primary"
|
||||||
<el-tab-pane
|
:loading="batchGenerating"
|
||||||
v-for="dimension in dimensions"
|
:disabled="batchGenerating || hasAnyGenerating"
|
||||||
:key="dimension.id"
|
@click="handleGenerateAll"
|
||||||
:name="dimension.id"
|
>
|
||||||
:label="dimension.name"
|
一键AI生成
|
||||||
>
|
<span v-if="batchGenerating"> ({{ batchProgress.current }}/{{ batchProgress.total }})</span>
|
||||||
<template #label>
|
</el-button>
|
||||||
<span class="dimension-tab">
|
|
||||||
<el-icon v-if="dimension.aiEnabled === 1"><Monitor /></el-icon>
|
|
||||||
{{ dimension.name }}
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
</el-tab-pane>
|
|
||||||
</el-tabs>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="dimension-list">
|
||||||
|
<div v-for="item in dimensionViews" :key="item.dimension.id" class="dimension-item">
|
||||||
|
<div class="dimension-item-header">
|
||||||
|
<div class="dimension-title">
|
||||||
|
<el-icon v-if="item.dimension.aiEnabled === 1"><Monitor /></el-icon>
|
||||||
|
<span>{{ item.dimension.name }}</span>
|
||||||
|
<el-tag size="small" :type="getStatusTag(item.state).type">{{ getStatusTag(item.state).label }}</el-tag>
|
||||||
|
<el-tag v-if="item.state.dirty" size="small" type="warning">未保存</el-tag>
|
||||||
|
<span v-else-if="item.state.lastSavedAt" class="saved-at">已保存 {{ item.state.lastSavedAt }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="dimension-actions">
|
||||||
|
<el-button size="small" @click="openDataSourceDialog(item.dimension.id!)">查看数据源</el-button>
|
||||||
|
<el-button size="small" @click="handleCopy(item.dimension.id!)" :disabled="!item.state.generatedResult">复制</el-button>
|
||||||
|
<el-button
|
||||||
|
size="small"
|
||||||
|
type="primary"
|
||||||
|
@click="handleSave(item.dimension.id!)"
|
||||||
|
:disabled="!item.state.finalContent"
|
||||||
|
>
|
||||||
|
保存
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 左右拆分布局 -->
|
|
||||||
<div class="split-container">
|
|
||||||
<!-- 左边:数据源面板 -->
|
|
||||||
<div class="left-panel" v-loading="dataSourcesLoading">
|
|
||||||
<DataSourcePanel
|
|
||||||
:data-sources="dataSources"
|
|
||||||
:data-sources-config="currentDimension?.dataSources"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 右边:分析面板 -->
|
|
||||||
<div class="right-panel">
|
|
||||||
<PromptEditor
|
<PromptEditor
|
||||||
ref="promptEditorRef"
|
:dimension="item.dimension"
|
||||||
:dimension="currentDimension"
|
|
||||||
:prisoner="prisoner"
|
:prisoner="prisoner"
|
||||||
@generate="handleGenerate"
|
:generating="item.state.generating"
|
||||||
@regenerate="handleRegenerate"
|
:generated-content="item.state.generatedResult"
|
||||||
|
@generate="(customPrompt?: string, systemPrompt?: string) => handleGenerate(item.dimension.id!, customPrompt, systemPrompt)"
|
||||||
|
@regenerate="(customPrompt?: string, systemPrompt?: string) => handleRegenerate(item.dimension.id!, customPrompt, systemPrompt)"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<LlmResultPanel
|
<LlmResultPanel
|
||||||
ref="llmResultRef"
|
:generating="item.state.generating"
|
||||||
:generating="generating"
|
:result="item.state.generatedResult"
|
||||||
:result="generatedResult"
|
:thinking="item.state.thinkingContent"
|
||||||
:thinking="thinkingContent"
|
@save="(content?: string) => handleSave(item.dimension.id!, content)"
|
||||||
@save="handleSave"
|
@copy="() => handleCopy(item.dimension.id!)"
|
||||||
@copy="handleCopy"
|
@regenerate="() => handleRegenerate(item.dimension.id!)"
|
||||||
|
@update-result="(content: string) => handleUpdateResult(item.dimension.id!, content)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 数据源弹窗 -->
|
||||||
|
<el-dialog
|
||||||
|
v-model="dataSourceDialogVisible"
|
||||||
|
:title="dataSourceDialogTitle"
|
||||||
|
width="70%"
|
||||||
|
top="8vh"
|
||||||
|
>
|
||||||
|
<div class="data-source-dialog-body" v-loading="dataSourceDialogLoading">
|
||||||
|
<DataSourcePanel
|
||||||
|
:data-sources="dataSourceDialogData"
|
||||||
|
:data-sources-config="dataSourceDialogDimension?.dataSources"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</el-dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { Monitor } from '@element-plus/icons-vue'
|
import { Monitor } from '@element-plus/icons-vue'
|
||||||
import { DimensionDataApi, DimensionDataSourcesVO, StreamApi, DimensionVO, ReportVO } from '@/api/prison/evaluation-report'
|
import { DimensionDataApi, DimensionDataSourcesVO, DimensionDataVO, StreamApi, DimensionVO, ReportVO } from '@/api/prison/evaluation-report'
|
||||||
import DataSourcePanel from './DataSourcePanel.vue'
|
import DataSourcePanel from './DataSourcePanel.vue'
|
||||||
import PromptEditor from './PromptEditor.vue'
|
import PromptEditor from './PromptEditor.vue'
|
||||||
import LlmResultPanel from './LlmResultPanel.vue'
|
import LlmResultPanel from './LlmResultPanel.vue'
|
||||||
@ -73,144 +94,463 @@ const emit = defineEmits<{
|
|||||||
}>()
|
}>()
|
||||||
|
|
||||||
// 状态
|
// 状态
|
||||||
const activeDimensionId = ref<number>()
|
const dataSourceDialogVisible = ref(false)
|
||||||
const dataSources = ref<DimensionDataSourcesVO | null>(null)
|
const dataSourceDialogDimensionId = ref<number | null>(null)
|
||||||
const dataSourcesLoading = ref(false)
|
const dataSourcesMap = reactive<Record<string, DimensionDataSourcesVO | null>>({})
|
||||||
const generating = ref(false)
|
const dataSourcesLoadingMap = reactive<Record<string, boolean>>({})
|
||||||
const generatedResult = ref('')
|
const dimensionDataLoading = ref(false)
|
||||||
const thinkingContent = ref('')
|
|
||||||
|
|
||||||
// Refs
|
// 结构化数据
|
||||||
const promptEditorRef = ref()
|
interface SectionData {
|
||||||
const llmResultRef = ref()
|
type: 'analysis' | 'final'
|
||||||
|
key: string
|
||||||
|
title: string
|
||||||
|
data: Record<string, any>
|
||||||
|
}
|
||||||
|
interface DimensionState {
|
||||||
|
generating: boolean
|
||||||
|
generatedResult: string
|
||||||
|
thinkingContent: string
|
||||||
|
analysisSections: SectionData[]
|
||||||
|
finalSections: SectionData[]
|
||||||
|
finalContent: string
|
||||||
|
eventSource: EventSource | null
|
||||||
|
lastCustomPrompt?: string
|
||||||
|
lastSystemPrompt?: string
|
||||||
|
dirty: boolean
|
||||||
|
lastSavedAt?: string
|
||||||
|
}
|
||||||
|
|
||||||
// 当前维度
|
const dimensionStates = reactive<Record<string, DimensionState>>({})
|
||||||
const currentDimension = computed(() => {
|
const batchGenerating = ref(false)
|
||||||
return props.dimensions.find(d => d.id === activeDimensionId.value)
|
const batchProgress = ref({ total: 0, current: 0 })
|
||||||
})
|
|
||||||
|
|
||||||
// EventSource 实例
|
const getDimensionState = (dimensionId: number): DimensionState => {
|
||||||
let eventSource: EventSource | null = null
|
const key = String(dimensionId)
|
||||||
|
if (!dimensionStates[key]) {
|
||||||
/** 初始化 */
|
dimensionStates[key] = {
|
||||||
onMounted(() => {
|
generating: false,
|
||||||
if (props.dimensions.length > 0) {
|
generatedResult: '',
|
||||||
activeDimensionId.value = props.dimensions[0].id
|
thinkingContent: '',
|
||||||
loadDataSources()
|
analysisSections: [],
|
||||||
|
finalSections: [],
|
||||||
|
finalContent: '',
|
||||||
|
eventSource: null,
|
||||||
|
lastCustomPrompt: undefined,
|
||||||
|
lastSystemPrompt: undefined,
|
||||||
|
dirty: false,
|
||||||
|
lastSavedAt: undefined
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return dimensionStates[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
const dimensionViews = computed(() => {
|
||||||
|
return props.dimensions
|
||||||
|
.filter(d => d.id !== undefined && d.id !== null)
|
||||||
|
.map(dimension => ({
|
||||||
|
dimension,
|
||||||
|
state: getDimensionState(dimension.id as number)
|
||||||
|
}))
|
||||||
})
|
})
|
||||||
|
|
||||||
/** 切换维度 */
|
type TagType = 'primary' | 'success' | 'warning' | 'info' | 'danger'
|
||||||
const handleDimensionChange = () => {
|
|
||||||
loadDataSources()
|
const getStatusTag = (state: DimensionState): { label: string; type: TagType } => {
|
||||||
// 重置生成结果
|
if (state.generating) {
|
||||||
generatedResult.value = ''
|
return { label: '生成中', type: 'warning' }
|
||||||
thinkingContent.value = ''
|
}
|
||||||
|
if (state.generatedResult) {
|
||||||
|
return { label: '已生成', type: 'success' }
|
||||||
|
}
|
||||||
|
return { label: '未生成', type: 'info' }
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasAnyGenerating = computed(() => dimensionViews.value.some(item => item.state.generating))
|
||||||
|
|
||||||
|
const buildSystemPrompt = (dimension?: DimensionVO) => {
|
||||||
|
if (!dimension) return undefined
|
||||||
|
const requirement = dimension.aiPrompt || `根据服刑人员的基本信息和相关数据,撰写关于「${dimension.name}」的客观评估。`
|
||||||
|
return `你是一位专业的监狱评估专家,负责撰写服刑人员的评估报告。
|
||||||
|
当前评估维度:${dimension.name}
|
||||||
|
|
||||||
|
评估要求:
|
||||||
|
${requirement}
|
||||||
|
|
||||||
|
请直接返回评估内容,格式为段落文本。`
|
||||||
|
}
|
||||||
|
const dataSourceDialogDimension = computed(() => {
|
||||||
|
return props.dimensions.find(d => d.id === dataSourceDialogDimensionId.value)
|
||||||
|
})
|
||||||
|
|
||||||
|
const dataSourceDialogData = computed(() => {
|
||||||
|
if (!dataSourceDialogDimensionId.value) return null
|
||||||
|
return dataSourcesMap[String(dataSourceDialogDimensionId.value)] || null
|
||||||
|
})
|
||||||
|
|
||||||
|
const dataSourceDialogLoading = computed(() => {
|
||||||
|
if (!dataSourceDialogDimensionId.value) return false
|
||||||
|
return dataSourcesLoadingMap[String(dataSourceDialogDimensionId.value)] || false
|
||||||
|
})
|
||||||
|
|
||||||
|
const dataSourceDialogTitle = computed(() => {
|
||||||
|
return dataSourceDialogDimension.value ? `数据源 - ${dataSourceDialogDimension.value.name}` : '数据源'
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
/** 打开数据源弹窗 */
|
||||||
|
const openDataSourceDialog = async (dimensionId: number) => {
|
||||||
|
dataSourceDialogDimensionId.value = dimensionId
|
||||||
|
dataSourceDialogVisible.value = true
|
||||||
|
await loadDataSources(dimensionId)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将结构化数据格式化为可读的 Markdown 文本
|
||||||
|
* 前端完全控制显示格式
|
||||||
|
*/
|
||||||
|
const formatStructuredContent = (sections: SectionData[]): string => {
|
||||||
|
const lines: string[] = []
|
||||||
|
|
||||||
|
for (const section of sections) {
|
||||||
|
lines.push(`## ${section.title}`)
|
||||||
|
lines.push('')
|
||||||
|
|
||||||
|
switch (section.key) {
|
||||||
|
case 'custom':
|
||||||
|
lines.push(section.data.content || '')
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'prisoner':
|
||||||
|
if (section.data.name) lines.push(`- **姓名**: ${section.data.name}`)
|
||||||
|
if (section.data.prisonerNo) lines.push(`- **编号**: ${section.data.prisonerNo}`)
|
||||||
|
if (section.data.crime) lines.push(`- **罪名**: ${section.data.crime}`)
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'consumption':
|
||||||
|
if (section.data.totalAmount !== undefined) {
|
||||||
|
lines.push(`- **总消费金额**: ${section.data.totalAmount} 元`)
|
||||||
|
}
|
||||||
|
if (section.data.recordCount !== undefined) {
|
||||||
|
lines.push(`- **消费记录数**: ${section.data.recordCount} 条`)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'score':
|
||||||
|
if (section.data.totalScore !== undefined) {
|
||||||
|
lines.push(`- **总得分**: ${section.data.totalScore} 分`)
|
||||||
|
}
|
||||||
|
if (section.data.recordCount !== undefined) {
|
||||||
|
lines.push(`- **考核记录数**: ${section.data.recordCount} 条`)
|
||||||
|
}
|
||||||
|
if (section.data.level !== undefined) {
|
||||||
|
lines.push(`- **平均等级**: ${section.data.level}`)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'risk':
|
||||||
|
if (section.data.riskLevel) lines.push(`- **风险等级**: ${section.data.riskLevel}`)
|
||||||
|
if (section.data.totalScore !== undefined) lines.push(`- **总分**: ${section.data.totalScore}`)
|
||||||
|
if (section.data.riskFactors) lines.push(`- **风险因素**: ${section.data.riskFactors}`)
|
||||||
|
if (section.data.suggestions) lines.push(`- **建议措施**: ${section.data.suggestions}`)
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'questionnaire':
|
||||||
|
const records = section.data.records as Array<Record<string, any>>
|
||||||
|
if (records && records.length > 0) {
|
||||||
|
for (const record of records) {
|
||||||
|
let item = `- ${record.questionnaireName || '未知问卷'}`
|
||||||
|
if (record.totalScore !== undefined) {
|
||||||
|
item += ` (得分: ${record.totalScore})`
|
||||||
|
}
|
||||||
|
lines.push(item)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
lines.push('暂无问卷记录')
|
||||||
|
}
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'summary':
|
||||||
|
lines.push(section.data.content || '暂无分析建议')
|
||||||
|
break
|
||||||
|
|
||||||
|
default:
|
||||||
|
// 通用处理:遍历 data 对象
|
||||||
|
for (const [key, value] of Object.entries(section.data)) {
|
||||||
|
if (value !== null && value !== undefined) {
|
||||||
|
lines.push(`- **${key}**: ${value}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lines.push('')
|
||||||
|
}
|
||||||
|
|
||||||
|
return lines.join('\n')
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 加载数据源 */
|
/** 加载数据源 */
|
||||||
const loadDataSources = async () => {
|
const loadDataSources = async (dimensionId: number) => {
|
||||||
if (!activeDimensionId.value) return
|
const key = String(dimensionId)
|
||||||
|
dataSourcesLoadingMap[key] = true
|
||||||
dataSourcesLoading.value = true
|
|
||||||
try {
|
try {
|
||||||
dataSources.value = await DimensionDataApi.getDimensionDataSources(
|
dataSourcesMap[key] = await DimensionDataApi.getDimensionDataSources(
|
||||||
activeDimensionId.value,
|
dimensionId,
|
||||||
props.prisonerId
|
props.prisonerId
|
||||||
)
|
)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('加载数据源失败:', error)
|
console.error('加载数据源失败:', error)
|
||||||
} finally {
|
} finally {
|
||||||
dataSourcesLoading.value = false
|
dataSourcesLoadingMap[key] = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const resetDimensionStates = () => {
|
||||||
|
props.dimensions.forEach(dimension => {
|
||||||
|
if (!dimension.id) return
|
||||||
|
const state = getDimensionState(dimension.id)
|
||||||
|
state.generating = false
|
||||||
|
state.generatedResult = ''
|
||||||
|
state.thinkingContent = ''
|
||||||
|
state.analysisSections = []
|
||||||
|
state.finalSections = []
|
||||||
|
state.finalContent = ''
|
||||||
|
state.dirty = false
|
||||||
|
state.lastSavedAt = undefined
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const applyDimensionData = (list: DimensionDataVO[]) => {
|
||||||
|
list.forEach(item => {
|
||||||
|
if (!item.dimensionId) return
|
||||||
|
const state = getDimensionState(item.dimensionId)
|
||||||
|
if (item.aiAnalysis) {
|
||||||
|
state.finalContent = item.aiAnalysis
|
||||||
|
state.generatedResult = item.aiAnalysis
|
||||||
|
state.dirty = false
|
||||||
|
state.lastSavedAt = item.modifiedTime || item.createTime
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadDimensionData = async () => {
|
||||||
|
if (!props.reportId) return
|
||||||
|
dimensionDataLoading.value = true
|
||||||
|
try {
|
||||||
|
resetDimensionStates()
|
||||||
|
const list = await DimensionDataApi.getDimensionDataListByReportId(props.reportId)
|
||||||
|
applyDimensionData(list || [])
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载维度内容失败:', error)
|
||||||
|
} finally {
|
||||||
|
dimensionDataLoading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 开始生成 */
|
/** 开始生成 */
|
||||||
const handleGenerate = async (customPrompt?: string) => {
|
const handleGenerate = async (dimensionId: number, customPrompt?: string, systemPrompt?: string): Promise<void> => {
|
||||||
if (!activeDimensionId.value) return
|
const state = getDimensionState(dimensionId)
|
||||||
|
const promptToUse = customPrompt !== undefined ? customPrompt : state.lastCustomPrompt
|
||||||
|
let systemPromptToUse = systemPrompt !== undefined ? systemPrompt : state.lastSystemPrompt
|
||||||
|
|
||||||
|
if (customPrompt !== undefined) {
|
||||||
|
state.lastCustomPrompt = customPrompt
|
||||||
|
}
|
||||||
|
if (systemPrompt !== undefined) {
|
||||||
|
state.lastSystemPrompt = systemPrompt
|
||||||
|
}
|
||||||
|
if (!systemPromptToUse) {
|
||||||
|
const dimension = props.dimensions.find(d => d.id === dimensionId)
|
||||||
|
systemPromptToUse = buildSystemPrompt(dimension)
|
||||||
|
if (systemPromptToUse) {
|
||||||
|
state.lastSystemPrompt = systemPromptToUse
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 关闭之前的连接
|
// 关闭之前的连接
|
||||||
closeEventSource()
|
closeEventSource(dimensionId)
|
||||||
|
|
||||||
generating.value = true
|
state.generating = true
|
||||||
generatedResult.value = ''
|
state.generatedResult = ''
|
||||||
thinkingContent.value = ''
|
state.thinkingContent = ''
|
||||||
|
state.analysisSections = []
|
||||||
|
state.finalSections = []
|
||||||
|
state.finalContent = ''
|
||||||
|
state.dirty = false
|
||||||
|
|
||||||
try {
|
try {
|
||||||
eventSource = await StreamApi.streamGenerateDimension(
|
state.eventSource = await StreamApi.streamGenerateDimension(
|
||||||
activeDimensionId.value,
|
dimensionId,
|
||||||
props.prisonerId,
|
props.prisonerId,
|
||||||
customPrompt
|
promptToUse,
|
||||||
|
systemPromptToUse
|
||||||
)
|
)
|
||||||
|
|
||||||
eventSource.onmessage = (event) => {
|
// 监听开始事件 - 包含维度基本信息
|
||||||
const content = event.data
|
state.eventSource.addEventListener('start', (event: MessageEvent) => {
|
||||||
if (content.startsWith('**思考过程**:')) {
|
try {
|
||||||
// 思考过程内容,逐字显示
|
const data = JSON.parse(event.data)
|
||||||
const thinkingText = content.replace('**思考过程**:', '')
|
console.log('SSE 开始:', data)
|
||||||
thinkingContent.value += thinkingText
|
// 可以在这里处理维度名称、描述等信息
|
||||||
} else {
|
} catch (e) {
|
||||||
// 正式内容
|
console.error('解析 start 事件失败:', e)
|
||||||
generatedResult.value += content
|
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
|
||||||
eventSource.onerror = () => {
|
// 监听数据段落事件 - 结构化数据
|
||||||
generating.value = false
|
// section payload: { type: 'analysis' | 'final', key, title, data }
|
||||||
closeEventSource()
|
state.eventSource.addEventListener('section', (event: MessageEvent) => {
|
||||||
}
|
try {
|
||||||
|
const section = JSON.parse(event.data) as SectionData
|
||||||
|
if (section.type === 'analysis') {
|
||||||
|
state.analysisSections.push(section)
|
||||||
|
state.thinkingContent = formatStructuredContent(state.analysisSections)
|
||||||
|
} else if (section.type === 'final') {
|
||||||
|
state.finalSections.push(section)
|
||||||
|
state.finalContent = formatStructuredContent(state.finalSections)
|
||||||
|
state.generatedResult = state.finalContent
|
||||||
|
state.dirty = true
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('解析 section 事件失败:', e)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
eventSource.onopen = () => {
|
return await new Promise<void>((resolve) => {
|
||||||
console.log('SSE 连接已建立')
|
const finalize = () => {
|
||||||
}
|
closeEventSource(dimensionId)
|
||||||
|
resolve()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听完成事件
|
||||||
|
state.eventSource!.addEventListener('complete', (event: MessageEvent) => {
|
||||||
|
console.log('SSE 完成:', event.data)
|
||||||
|
finalize()
|
||||||
|
})
|
||||||
|
|
||||||
|
// 监听错误事件(服务端发送的)
|
||||||
|
state.eventSource!.addEventListener('error', (event: MessageEvent) => {
|
||||||
|
console.error('SSE 服务端错误:', event.data)
|
||||||
|
finalize()
|
||||||
|
})
|
||||||
|
|
||||||
|
// 连接错误(网络等)
|
||||||
|
state.eventSource!.onerror = (error) => {
|
||||||
|
console.error('SSE 连接错误:', error)
|
||||||
|
finalize()
|
||||||
|
}
|
||||||
|
|
||||||
|
state.eventSource!.onopen = () => {
|
||||||
|
console.log('SSE 连接已建立')
|
||||||
|
}
|
||||||
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('启动流式生成失败:', error)
|
console.error('启动流式生成失败:', error)
|
||||||
generating.value = false
|
state.generating = false
|
||||||
|
closeEventSource(dimensionId)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 重新生成 */
|
/** 重新生成 */
|
||||||
const handleRegenerate = () => {
|
const handleRegenerate = (dimensionId: number, customPrompt?: string, systemPrompt?: string) => {
|
||||||
const customPrompt = promptEditorRef.value?.getCustomPrompt()
|
if (customPrompt !== undefined || systemPrompt !== undefined) {
|
||||||
handleGenerate(customPrompt)
|
handleGenerate(dimensionId, customPrompt, systemPrompt)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const state = getDimensionState(dimensionId)
|
||||||
|
handleGenerate(dimensionId, state.lastCustomPrompt, state.lastSystemPrompt)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 关闭 EventSource 连接 */
|
/** 关闭 EventSource 连接 */
|
||||||
const closeEventSource = () => {
|
const closeEventSource = (dimensionId: number) => {
|
||||||
if (eventSource) {
|
const state = getDimensionState(dimensionId)
|
||||||
eventSource.close()
|
if (state.eventSource) {
|
||||||
eventSource = null
|
state.eventSource.close()
|
||||||
|
state.eventSource = null
|
||||||
}
|
}
|
||||||
generating.value = false
|
state.generating = false
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 保存结果 */
|
/** 保存结果 */
|
||||||
const handleSave = () => {
|
const handleSave = (dimensionId: number, content?: string) => {
|
||||||
if (!activeDimensionId.value || !generatedResult.value) return
|
const state = getDimensionState(dimensionId)
|
||||||
|
if (content !== undefined) {
|
||||||
|
state.finalContent = content
|
||||||
|
state.generatedResult = content
|
||||||
|
state.dirty = true
|
||||||
|
}
|
||||||
|
if (!state.finalContent) return
|
||||||
|
state.dirty = false
|
||||||
|
state.lastSavedAt = new Date().toLocaleString()
|
||||||
emit('save', {
|
emit('save', {
|
||||||
dimensionId: activeDimensionId.value,
|
dimensionId,
|
||||||
content: generatedResult.value
|
content: state.finalContent
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleUpdateResult = (dimensionId: number, content: string) => {
|
||||||
|
const state = getDimensionState(dimensionId)
|
||||||
|
state.finalContent = content
|
||||||
|
state.generatedResult = content
|
||||||
|
state.dirty = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleGenerateAll = async () => {
|
||||||
|
if (batchGenerating.value) return
|
||||||
|
const targets = dimensionViews.value
|
||||||
|
.filter(item => item.dimension.aiEnabled === 1)
|
||||||
|
.map(item => item.dimension.id as number)
|
||||||
|
|
||||||
|
if (targets.length === 0) return
|
||||||
|
|
||||||
|
batchGenerating.value = true
|
||||||
|
batchProgress.value = { total: targets.length, current: 0 }
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (let i = 0; i < targets.length; i += 1) {
|
||||||
|
batchProgress.value = { total: targets.length, current: i + 1 }
|
||||||
|
await handleGenerate(targets[i])
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
batchGenerating.value = false
|
||||||
|
batchProgress.value = { total: 0, current: 0 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** 复制内容 */
|
/** 复制内容 */
|
||||||
const handleCopy = () => {
|
const handleCopy = (dimensionId: number) => {
|
||||||
if (!generatedResult.value) return
|
const state = getDimensionState(dimensionId)
|
||||||
navigator.clipboard.writeText(generatedResult.value)
|
if (!state.generatedResult) return
|
||||||
|
navigator.clipboard.writeText(state.generatedResult)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 组件卸载时关闭连接 */
|
/** 组件卸载时关闭连接 */
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
closeEventSource()
|
props.dimensions.forEach(dimension => {
|
||||||
|
if (dimension.id) {
|
||||||
|
closeEventSource(dimension.id)
|
||||||
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// 监听维度变化,自动加载数据
|
// 监听维度变化,初始化状态
|
||||||
watch(() => props.dimensions, (newDimensions) => {
|
watch(() => props.dimensions, (newDimensions) => {
|
||||||
if (newDimensions.length > 0 && !activeDimensionId.value) {
|
newDimensions.forEach(dimension => {
|
||||||
activeDimensionId.value = newDimensions[0].id
|
if (dimension.id) {
|
||||||
loadDataSources()
|
getDimensionState(dimension.id)
|
||||||
}
|
}
|
||||||
|
})
|
||||||
}, { immediate: true })
|
}, { immediate: true })
|
||||||
|
|
||||||
|
watch(
|
||||||
|
[() => props.reportId, () => props.dimensions.map(d => d.id).filter(Boolean).join(',')],
|
||||||
|
([reportId]) => {
|
||||||
|
if (!reportId) return
|
||||||
|
loadDimensionData()
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@ -220,40 +560,53 @@ watch(() => props.dimensions, (newDimensions) => {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dimension-tabs {
|
.dimension-toolbar {
|
||||||
padding: 0 20px;
|
|
||||||
background: var(--el-bg-color-page);
|
|
||||||
border-bottom: 1px solid var(--el-border-color);
|
|
||||||
|
|
||||||
:deep(.el-tabs__header) {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dimension-tab {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 6px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.split-container {
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
overflow: hidden;
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
padding: 12px 20px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.left-panel {
|
.dimension-list {
|
||||||
width: 40%;
|
flex: 1;
|
||||||
border-right: 1px solid var(--el-border-color);
|
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
padding: 16px;
|
padding: 16px 20px;
|
||||||
background: var(--el-bg-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.right-panel {
|
|
||||||
width: 60%;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dimension-item {
|
||||||
|
background: var(--el-bg-color);
|
||||||
|
border: 1px solid var(--el-border-color-lighter);
|
||||||
|
border-radius: 8px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dimension-item-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-bottom: 1px solid var(--el-border-color-lighter);
|
||||||
|
background: var(--el-bg-color-page);
|
||||||
|
|
||||||
|
.dimension-title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.saved-at {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--el-text-color-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-source-dialog-body {
|
||||||
|
max-height: 70vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -26,6 +26,24 @@
|
|||||||
<span>生成结果</span>
|
<span>生成结果</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-actions">
|
<div class="header-actions">
|
||||||
|
<el-button
|
||||||
|
v-if="editable"
|
||||||
|
link
|
||||||
|
type="primary"
|
||||||
|
@click="toggleEdit"
|
||||||
|
>
|
||||||
|
<el-icon><Edit /></el-icon>
|
||||||
|
{{ isEditing ? '取消编辑' : '手动编辑' }}
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
v-if="editable && isEditing"
|
||||||
|
link
|
||||||
|
type="success"
|
||||||
|
@click="applyEdit"
|
||||||
|
>
|
||||||
|
<el-icon><Check /></el-icon>
|
||||||
|
应用
|
||||||
|
</el-button>
|
||||||
<el-button link type="primary" @click="$emit('copy')">
|
<el-button link type="primary" @click="$emit('copy')">
|
||||||
<el-icon><CopyDocument /></el-icon>
|
<el-icon><CopyDocument /></el-icon>
|
||||||
复制
|
复制
|
||||||
@ -33,16 +51,42 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="result-content" v-if="result">
|
<div class="result-content" v-if="result || thinking || isEditing">
|
||||||
<MarkdownView :content="result" />
|
<div v-if="thinking" class="analysis-section">
|
||||||
|
<div class="analysis-header">
|
||||||
|
<el-icon><Warning /></el-icon>
|
||||||
|
<span>分析过程</span>
|
||||||
|
</div>
|
||||||
|
<div class="analysis-content">
|
||||||
|
<MarkdownView :content="thinking" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="result || isEditing"
|
||||||
|
class="final-section"
|
||||||
|
@dblclick="enterEdit"
|
||||||
|
>
|
||||||
|
<div v-if="isEditing" class="final-edit">
|
||||||
|
<el-input
|
||||||
|
v-model="editValue"
|
||||||
|
type="textarea"
|
||||||
|
:rows="12"
|
||||||
|
placeholder="可直接编辑维度结果内容"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<MarkdownView v-else :content="result" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<el-empty v-else description="暂无生成结果,点击上方按钮开始生成" />
|
<el-empty v-else description="暂无生成结果,可手动编辑">
|
||||||
|
<el-button type="primary" @click="toggleEdit">手动编辑</el-button>
|
||||||
|
</el-empty>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 底部操作栏 -->
|
<!-- 底部操作栏 -->
|
||||||
<div class="footer-actions" v-if="result && !generating">
|
<div class="footer-actions" v-if="!generating && (result || isEditing)">
|
||||||
<el-button type="primary" @click="$emit('save')">
|
<el-button type="primary" @click="handleSaveClick">
|
||||||
<el-icon><Check /></el-icon>
|
<el-icon><Check /></el-icon>
|
||||||
保存到评估
|
保存到评估
|
||||||
</el-button>
|
</el-button>
|
||||||
@ -55,7 +99,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { Loading, Warning, DocumentChecked, CopyDocument, Check, Refresh } from '@element-plus/icons-vue'
|
import { ref, computed, watch } from 'vue'
|
||||||
|
import { Loading, Warning, DocumentChecked, CopyDocument, Check, Refresh, Edit } from '@element-plus/icons-vue'
|
||||||
|
|
||||||
defineOptions({ name: 'LlmResultPanel' })
|
defineOptions({ name: 'LlmResultPanel' })
|
||||||
|
|
||||||
@ -63,13 +108,64 @@ const props = defineProps<{
|
|||||||
generating: boolean
|
generating: boolean
|
||||||
result: string
|
result: string
|
||||||
thinking: string
|
thinking: string
|
||||||
|
editable?: boolean
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'save'): void
|
(e: 'save', content?: string): void
|
||||||
(e: 'copy'): void
|
(e: 'copy'): void
|
||||||
(e: 'regenerate'): void
|
(e: 'regenerate'): void
|
||||||
|
(e: 'update-result', content: string): void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
const isEditing = ref(false)
|
||||||
|
const editValue = ref('')
|
||||||
|
|
||||||
|
const editable = computed(() => props.editable !== false)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.result,
|
||||||
|
(value) => {
|
||||||
|
if (!isEditing.value) {
|
||||||
|
editValue.value = value || ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
const toggleEdit = () => {
|
||||||
|
if (!editable.value) return
|
||||||
|
if (isEditing.value) {
|
||||||
|
isEditing.value = false
|
||||||
|
editValue.value = props.result || ''
|
||||||
|
} else {
|
||||||
|
isEditing.value = true
|
||||||
|
editValue.value = props.result || ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const applyEdit = () => {
|
||||||
|
if (!editable.value) return
|
||||||
|
emit('update-result', editValue.value)
|
||||||
|
isEditing.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const enterEdit = () => {
|
||||||
|
if (!editable.value) return
|
||||||
|
if (!isEditing.value) {
|
||||||
|
toggleEdit()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSaveClick = () => {
|
||||||
|
if (editable.value && isEditing.value) {
|
||||||
|
emit('update-result', editValue.value)
|
||||||
|
emit('save', editValue.value)
|
||||||
|
isEditing.value = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
emit('save')
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
@ -99,7 +195,6 @@ const MarkdownView = defineComponent({
|
|||||||
nodes.push(h('h1', { key: index, class: 'markdown-h1' }, line.slice(2)))
|
nodes.push(h('h1', { key: index, class: 'markdown-h1' }, line.slice(2)))
|
||||||
} else {
|
} else {
|
||||||
// 处理粗体文本
|
// 处理粗体文本
|
||||||
let text = line
|
|
||||||
const boldRegex = /\*\*(.*?)\*\*/g
|
const boldRegex = /\*\*(.*?)\*\*/g
|
||||||
const parts: any[] = []
|
const parts: any[] = []
|
||||||
let lastIndex = 0
|
let lastIndex = 0
|
||||||
@ -216,6 +311,22 @@ export { MarkdownView }
|
|||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.analysis-section {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
padding: 12px;
|
||||||
|
background: var(--el-fill-color-light);
|
||||||
|
border-radius: 6px;
|
||||||
|
|
||||||
|
.analysis-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--el-color-warning);
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer-actions {
|
.footer-actions {
|
||||||
|
|||||||
@ -1,11 +1,24 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="prompt-editor">
|
<div class="prompt-editor">
|
||||||
|
<div class="prompt-toggle">
|
||||||
|
<div class="toggle-title">
|
||||||
|
<el-icon><Document /></el-icon>
|
||||||
|
<span>提示词与变量</span>
|
||||||
|
</div>
|
||||||
|
<el-button link type="primary" @click="showPromptDetails = !showPromptDetails">
|
||||||
|
{{ showPromptDetails ? '收起' : '展开' }}
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-show="showPromptDetails">
|
||||||
<!-- 系统提示词 -->
|
<!-- 系统提示词 -->
|
||||||
<div class="prompt-section">
|
<div class="prompt-section">
|
||||||
<div class="section-header">
|
<div class="section-header">
|
||||||
<el-icon><Document /></el-icon>
|
<div class="header-left">
|
||||||
<span>系统提示词</span>
|
<el-icon><Document /></el-icon>
|
||||||
<el-tag size="small" type="info">只读</el-tag>
|
<span>系统提示词</span>
|
||||||
|
<el-tag size="small" type="info">只读</el-tag>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<el-input
|
<el-input
|
||||||
type="textarea"
|
type="textarea"
|
||||||
@ -19,12 +32,16 @@
|
|||||||
<!-- 用户提示词 -->
|
<!-- 用户提示词 -->
|
||||||
<div class="prompt-section">
|
<div class="prompt-section">
|
||||||
<div class="section-header">
|
<div class="section-header">
|
||||||
<el-icon><Edit /></el-icon>
|
<div class="header-left">
|
||||||
<span>自定义提示词</span>
|
<el-icon><Edit /></el-icon>
|
||||||
<el-button link type="primary" @click="resetToDefault">
|
<span>自定义提示词</span>
|
||||||
<el-icon><Refresh /></el-icon>
|
</div>
|
||||||
恢复默认
|
<div class="header-actions">
|
||||||
</el-button>
|
<el-button link type="primary" @click="resetToDefault">
|
||||||
|
<el-icon><Refresh /></el-icon>
|
||||||
|
恢复默认
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<el-input
|
<el-input
|
||||||
type="textarea"
|
type="textarea"
|
||||||
@ -37,12 +54,16 @@
|
|||||||
<!-- 变量预览 -->
|
<!-- 变量预览 -->
|
||||||
<div class="prompt-section">
|
<div class="prompt-section">
|
||||||
<div class="section-header">
|
<div class="section-header">
|
||||||
<el-icon><InfoFilled /></el-icon>
|
<div class="header-left">
|
||||||
<span>变量预览</span>
|
<el-icon><InfoFilled /></el-icon>
|
||||||
<el-button link type="primary" @click="showVariableHelp = true">
|
<span>变量预览</span>
|
||||||
<el-icon><QuestionFilled /></el-icon>
|
</div>
|
||||||
帮助
|
<div class="header-actions">
|
||||||
</el-button>
|
<el-button link type="primary" @click="showVariableHelp = true">
|
||||||
|
<el-icon><QuestionFilled /></el-icon>
|
||||||
|
帮助
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="variable-preview">
|
<div class="variable-preview">
|
||||||
<el-tag
|
<el-tag
|
||||||
@ -55,6 +76,7 @@
|
|||||||
</el-tag>
|
</el-tag>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 操作按钮 -->
|
<!-- 操作按钮 -->
|
||||||
<div class="action-buttons">
|
<div class="action-buttons">
|
||||||
@ -103,18 +125,21 @@ defineOptions({ name: 'PromptEditor' })
|
|||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
dimension: DimensionVO | undefined
|
dimension: DimensionVO | undefined
|
||||||
prisoner?: { name?: string; prisonerNo?: string }
|
prisoner?: { name?: string; prisonerNo?: string }
|
||||||
|
generating?: boolean
|
||||||
|
generatedContent?: string
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'generate', customPrompt?: string): void
|
(e: 'generate', customPrompt?: string, systemPrompt?: string): void
|
||||||
(e: 'regenerate', customPrompt?: string): void
|
(e: 'regenerate', customPrompt?: string, systemPrompt?: string): void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
// 生成状态(从父组件传入)
|
// 生成状态(优先使用 props,其次从注入获取)
|
||||||
const generating = inject<Ref<boolean>>('generating', ref(false))
|
const injectedGenerating = inject<Ref<boolean>>('generating', ref(false))
|
||||||
|
const injectedGeneratedContent = inject<Ref<string>>('generatedContent', ref(''))
|
||||||
|
|
||||||
// 已生成的内容(从父组件传入)
|
const generating = computed(() => props.generating ?? injectedGenerating.value)
|
||||||
const generatedContent = inject<Ref<string>>('generatedContent', ref(''))
|
const generatedContent = computed(() => props.generatedContent ?? injectedGeneratedContent.value)
|
||||||
|
|
||||||
// 系统提示词
|
// 系统提示词
|
||||||
const systemPrompt = computed(() => {
|
const systemPrompt = computed(() => {
|
||||||
@ -140,6 +165,8 @@ const variables = [
|
|||||||
{ key: 'data.summary', desc: '数据汇总' }
|
{ key: 'data.summary', desc: '数据汇总' }
|
||||||
]
|
]
|
||||||
|
|
||||||
|
const showPromptDetails = ref(false)
|
||||||
|
|
||||||
// 显示变量帮助
|
// 显示变量帮助
|
||||||
const showVariableHelp = ref(false)
|
const showVariableHelp = ref(false)
|
||||||
|
|
||||||
@ -169,12 +196,12 @@ const insertVariable = (key: string) => {
|
|||||||
|
|
||||||
// 生成
|
// 生成
|
||||||
const handleGenerate = () => {
|
const handleGenerate = () => {
|
||||||
emit('generate', customPrompt.value || undefined)
|
emit('generate', customPrompt.value || undefined, systemPrompt.value || undefined)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 重新生成
|
// 重新生成
|
||||||
const handleRegenerate = () => {
|
const handleRegenerate = () => {
|
||||||
emit('regenerate', customPrompt.value || undefined)
|
emit('regenerate', customPrompt.value || undefined, systemPrompt.value || undefined)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取自定义提示词(供父组件调用)
|
// 获取自定义提示词(供父组件调用)
|
||||||
@ -192,6 +219,23 @@ defineExpose({ getCustomPrompt })
|
|||||||
border-bottom: 1px solid var(--el-border-color);
|
border-bottom: 1px solid var(--el-border-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.prompt-toggle {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
border-bottom: 1px dashed var(--el-border-color);
|
||||||
|
|
||||||
|
.toggle-title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--el-text-color-primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.prompt-section {
|
.prompt-section {
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
|
|
||||||
@ -202,11 +246,24 @@ defineExpose({ getCustomPrompt })
|
|||||||
.section-header {
|
.section-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: var(--el-text-color-primary);
|
color: var(--el-text-color-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.header-left {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.variable-preview {
|
.variable-preview {
|
||||||
|
|||||||
@ -218,15 +218,15 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
import { DICT_TYPE } from '@/utils/dict'
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
import { ReportApi, Report, ReportDimensionContent } from '@/api/prison/report'
|
import { ReportApi, Report, ReportVersionApi } from '@/api/prison/report'
|
||||||
import { PrisonerSelectApi, PrisonerBrief } from '@/api/prison/report'
|
import { PrisonerSelectApi, PrisonerBrief } from '@/api/prison/report'
|
||||||
import QuickCommentDialog from '@/views/prison/evaluation-mgmt/report/components/QuickCommentDialog.vue'
|
import QuickCommentDialog from '@/views/prison/evaluation-mgmt/report/components/QuickCommentDialog.vue'
|
||||||
import VersionHistoryDialog from '@/views/prison/evaluation-mgmt/report/components/VersionHistoryDialog.vue'
|
import VersionHistoryDialog from '@/views/prison/evaluation-mgmt/report/components/VersionHistoryDialog.vue'
|
||||||
import ReportPreviewDialog from '@/views/prison/evaluation-mgmt/report/components/ReportPreviewDialog.vue'
|
import ReportPreviewDialog from '@/views/prison/evaluation-mgmt/report/components/ReportPreviewDialog.vue'
|
||||||
import DimensionAnalysisPanel from '@/views/prison/evaluation-mgmt/report/DimensionAnalysisPanel.vue'
|
import DimensionAnalysisPanel from '@/views/prison/evaluation-mgmt/report/DimensionAnalysisPanel.vue'
|
||||||
import { DimensionApi, DimensionVO } from '@/api/prison/evaluation-report'
|
import { DimensionApi, DimensionVO, ReportUpdateReqVO } from '@/api/prison/evaluation-report'
|
||||||
|
|
||||||
/** 评估报告编辑页面 */
|
/** 评估报告编辑页面 */
|
||||||
defineOptions({ name: 'EvaluationReportForm' })
|
defineOptions({ name: 'EvaluationReportForm' })
|
||||||
@ -386,15 +386,17 @@ const updateDataSourceStatus = () => {
|
|||||||
dataSourceStatus.value = { total: 6, loaded: 6, complete: true }
|
dataSourceStatus.value = { total: 6, loaded: 6, complete: true }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TagType = 'primary' | 'success' | 'warning' | 'info' | 'danger'
|
||||||
|
|
||||||
/** 获取风险等级类型 */
|
/** 获取风险等级类型 */
|
||||||
const getRiskLevelType = (level?: number): string => {
|
const getRiskLevelType = (level?: number): TagType => {
|
||||||
const map: Record<number, string> = {
|
const map: Record<number, string> = {
|
||||||
1: 'success',
|
1: 'success',
|
||||||
2: 'warning',
|
2: 'warning',
|
||||||
3: 'danger',
|
3: 'danger',
|
||||||
4: 'danger'
|
4: 'danger'
|
||||||
}
|
}
|
||||||
return level ? map[level] || 'info' : 'info'
|
return (level ? map[level] || 'info' : 'info') as TagType
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 获取风险等级标签 */
|
/** 获取风险等级标签 */
|
||||||
@ -408,24 +410,34 @@ const getRiskLevelLabel = (level?: number): string => {
|
|||||||
return level ? map[level] || '-' : '未评估'
|
return level ? map[level] || '-' : '未评估'
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 标记维度已修改 */
|
|
||||||
const markDimensionModified = (dimension: ReportDimensionContent) => {
|
|
||||||
dimension.isAiGenerated = false
|
|
||||||
dimension.lastModifyTime = new Date().toISOString().slice(0, 19).replace('T', ' ')
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 保存维度内容 */
|
/** 保存维度内容 */
|
||||||
const handleSaveDimension = async (data: { dimensionId: number; content: string }) => {
|
const handleSaveDimension = async (data: { dimensionId: number; content: string }) => {
|
||||||
if (!currentReport.value) return
|
if (!currentReport.value) return
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 更新维度内容
|
if (!currentReport.value.dimensions) {
|
||||||
const dimension = currentReport.value.dimensions.find(d => d.dimensionId === data.dimensionId)
|
currentReport.value.dimensions = []
|
||||||
if (dimension) {
|
|
||||||
dimension.content = data.content
|
|
||||||
dimension.isAiGenerated = true
|
|
||||||
dimension.aiGenerateTime = new Date().toLocaleString()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const dimensionConfig = dimensions.value.find(d => d.id === data.dimensionId)
|
||||||
|
let dimension = currentReport.value.dimensions.find(d => d.dimensionId === data.dimensionId)
|
||||||
|
|
||||||
|
if (!dimension) {
|
||||||
|
dimension = {
|
||||||
|
dimensionId: data.dimensionId,
|
||||||
|
dimensionName: dimensionConfig?.name || `维度${data.dimensionId}`,
|
||||||
|
content: '',
|
||||||
|
isAiGenerated: false,
|
||||||
|
dataSources: dimensionConfig?.dataSources ? JSON.stringify(dimensionConfig.dataSources) : undefined,
|
||||||
|
enableAi: dimensionConfig?.aiEnabled === 1,
|
||||||
|
sort: dimensionConfig?.sort
|
||||||
|
}
|
||||||
|
currentReport.value.dimensions.push(dimension)
|
||||||
|
}
|
||||||
|
|
||||||
|
dimension.content = data.content
|
||||||
|
dimension.isAiGenerated = true
|
||||||
|
dimension.aiGenerateTime = new Date().toLocaleString()
|
||||||
message.success('维度内容已保存')
|
message.success('维度内容已保存')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('保存维度内容失败:', error)
|
console.error('保存维度内容失败:', error)
|
||||||
@ -433,28 +445,6 @@ const handleSaveDimension = async (data: { dimensionId: number; content: string
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** AI生成单个维度 */
|
|
||||||
const handleAiGenerate = async (dimension: ReportDimensionContent) => {
|
|
||||||
if (!currentReport.value) return
|
|
||||||
|
|
||||||
generatingDimensionId.value = dimension.dimensionId
|
|
||||||
aiGenerating.value = true
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 调用AI生成接口
|
|
||||||
await ReportApi.generateReportByAi(currentReport.value.id, [dimension.dimensionId])
|
|
||||||
|
|
||||||
// 重新获取报告数据
|
|
||||||
const data = await ReportApi.getReport(currentReport.value.id)
|
|
||||||
currentReport.value = data
|
|
||||||
message.success(t('prison.report.aiGenerateComplete'))
|
|
||||||
} catch (error: any) {
|
|
||||||
message.error(error?.message || t('prison.report.aiGenerateFailed'))
|
|
||||||
} finally {
|
|
||||||
aiGenerating.value = false
|
|
||||||
generatingDimensionId.value = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** AI生成全部 */
|
/** AI生成全部 */
|
||||||
const handleAiGenerateAll = async () => {
|
const handleAiGenerateAll = async () => {
|
||||||
@ -484,8 +474,17 @@ const handleSaveDraft = async () => {
|
|||||||
|
|
||||||
saving.value = true
|
saving.value = true
|
||||||
try {
|
try {
|
||||||
// 调用保存接口
|
// 只传递需要更新的字段,避免传递大量冗余数据
|
||||||
await ReportApi.updateReport(currentReport.value)
|
const updateData: ReportUpdateReqVO = {
|
||||||
|
id: currentReport.value.id,
|
||||||
|
dimensions: JSON.stringify(currentReport.value.dimensions),
|
||||||
|
conclusion: currentReport.value.conclusion,
|
||||||
|
suggestions: currentReport.value.suggestions,
|
||||||
|
riskLevel: currentReport.value.riskLevel,
|
||||||
|
status: currentReport.value.status,
|
||||||
|
remark: currentReport.value.remark
|
||||||
|
}
|
||||||
|
await ReportApi.updateReport(updateData)
|
||||||
|
|
||||||
lastSaveTime.value = new Date().toLocaleTimeString()
|
lastSaveTime.value = new Date().toLocaleTimeString()
|
||||||
message.success(t('common.saveSuccess'))
|
message.success(t('common.saveSuccess'))
|
||||||
@ -509,18 +508,6 @@ const handleSubmitReview = async () => {
|
|||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 恢复AI原始内容 */
|
|
||||||
const handleRestoreAiContent = async (dimension: ReportDimensionContent) => {
|
|
||||||
try {
|
|
||||||
await message.confirm(t('prison.report.restoreAiConfirm'))
|
|
||||||
// 调用接口获取AI原始内容
|
|
||||||
await ReportApi.generateReportByAi(currentReport.value!.id, [dimension.dimensionId])
|
|
||||||
// 重新获取报告数据
|
|
||||||
const data = await ReportApi.getReport(currentReport.value!.id)
|
|
||||||
currentReport.value = data
|
|
||||||
message.success(t('prison.report.restoreAiSuccess'))
|
|
||||||
} catch {}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 预览报告 */
|
/** 预览报告 */
|
||||||
const handlePreview = () => {
|
const handlePreview = () => {
|
||||||
@ -530,12 +517,15 @@ const handlePreview = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 导出报告 */
|
/** 导出报告 */
|
||||||
const handleExport = async (format: 'pdf' | 'word') => {
|
const handleExport = async (_format: 'pdf' | 'word') => {
|
||||||
if (!currentReport.value) return
|
if (!currentReport.value) return
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const data = await ReportApi.exportReport(currentReport.value.id, format)
|
await ReportApi.exportReportExcel({
|
||||||
const fileName = `${currentReport.value.reportNo}_${currentReport.value.prisonerName}.${format}`
|
pageNo: 1,
|
||||||
|
pageSize: 1,
|
||||||
|
prisonerNo: currentReport.value.prisonerNo
|
||||||
|
})
|
||||||
message.success('导出成功')
|
message.success('导出成功')
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
@ -547,6 +537,7 @@ const handleShowQuickComment = () => {
|
|||||||
|
|
||||||
/** 插入快捷评语 */
|
/** 插入快捷评语 */
|
||||||
const insertComment = (comment: string) => {
|
const insertComment = (comment: string) => {
|
||||||
|
if (!comment) return
|
||||||
message.success(t('prison.report.commentInserted'))
|
message.success(t('prison.report.commentInserted'))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -41,10 +41,10 @@
|
|||||||
<el-input v-model="formData.applicableCrowd" placeholder="请输入适用人群,如:新入监罪犯" />
|
<el-input v-model="formData.applicableCrowd" placeholder="请输入适用人群,如:新入监罪犯" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="模板描述" prop="description">
|
<el-form-item label="模板描述" prop="description">
|
||||||
<el-input v-model="formData.description" type="textarea" rows="3" placeholder="请输入模板描述" />
|
<el-input v-model="formData.description" type="textarea" :rows="3" placeholder="请输入模板描述" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="AI提示词" prop="aiPrompt">
|
<el-form-item label="AI提示词" prop="aiPrompt">
|
||||||
<el-input v-model="formData.aiPrompt" type="textarea" rows="4" placeholder="请输入AI提示词,用于AI生成报告时参考" />
|
<el-input v-model="formData.aiPrompt" type="textarea" :rows="4" placeholder="请输入AI提示词,用于AI生成报告时参考" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="允许AI生成" prop="aiEnabled">
|
<el-form-item label="允许AI生成" prop="aiEnabled">
|
||||||
<el-switch v-model="formData.aiEnabled" :active-value="1" :inactive-value="0" />
|
<el-switch v-model="formData.aiEnabled" :active-value="1" :inactive-value="0" />
|
||||||
@ -53,7 +53,7 @@
|
|||||||
<el-input-number v-model="formData.sort" :min="0" :max="999" controls-position="right" />
|
<el-input-number v-model="formData.sort" :min="0" :max="999" controls-position="right" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="备注" prop="remark">
|
<el-form-item label="备注" prop="remark">
|
||||||
<el-input v-model="formData.remark" type="textarea" rows="2" placeholder="请输入备注信息" />
|
<el-input v-model="formData.remark" type="textarea" :rows="2" placeholder="请输入备注信息" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="状态" prop="status">
|
<el-form-item label="状态" prop="status">
|
||||||
<el-radio-group v-model="formData.status">
|
<el-radio-group v-model="formData.status">
|
||||||
|
|||||||
@ -52,26 +52,6 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 综合结论与建议 -->
|
|
||||||
<div class="conclusion-section">
|
|
||||||
<div class="section-header">
|
|
||||||
<span class="section-title">综合结论与建议</span>
|
|
||||||
</div>
|
|
||||||
<el-row :gutter="20">
|
|
||||||
<el-col :span="12">
|
|
||||||
<div class="conclusion-box">
|
|
||||||
<div class="box-header">综合结论</div>
|
|
||||||
<el-input v-model="selectedReport.conclusion" type="textarea" :rows="6" placeholder="请输入综合结论" />
|
|
||||||
</div>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="12">
|
|
||||||
<div class="conclusion-box">
|
|
||||||
<div class="box-header">改造建议</div>
|
|
||||||
<el-input v-model="selectedReport.suggestions" type="textarea" :rows="6" placeholder="请输入改造建议" />
|
|
||||||
</div>
|
|
||||||
</el-col>
|
|
||||||
</el-row>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-else-if="!loading">
|
<template v-else-if="!loading">
|
||||||
@ -86,6 +66,7 @@ import { DICT_TYPE, getIntDictOptions, getDictLabel } from '@/utils/dict'
|
|||||||
import { formatDateTime } from '@/utils/formatTime'
|
import { formatDateTime } from '@/utils/formatTime'
|
||||||
import download from '@/utils/download'
|
import download from '@/utils/download'
|
||||||
import { ReportApi, ReportVO, DimensionDataApi, DimensionDataVO, DimensionApi, DimensionVO } from '@/api/prison/evaluation-report'
|
import { ReportApi, ReportVO, DimensionDataApi, DimensionDataVO, DimensionApi, DimensionVO } from '@/api/prison/evaluation-report'
|
||||||
|
import { PrisonerApi } from '@/api/prison/prisoner'
|
||||||
import DimensionAnalysisPanel from '@/views/prison/evaluation-mgmt/report/DimensionAnalysisPanel.vue'
|
import DimensionAnalysisPanel from '@/views/prison/evaluation-mgmt/report/DimensionAnalysisPanel.vue'
|
||||||
|
|
||||||
defineOptions({ name: 'ReportEditDrawer' })
|
defineOptions({ name: 'ReportEditDrawer' })
|
||||||
@ -123,6 +104,15 @@ const loadReportDetail = async (id: number) => {
|
|||||||
dimensionDataList.value = await DimensionDataApi.getDimensionDataListByReportId(id)
|
dimensionDataList.value = await DimensionDataApi.getDimensionDataListByReportId(id)
|
||||||
drawerTitle.value = selectedReport.value?.title || `${selectedReport.value?.prisonerName} - 评估报告`
|
drawerTitle.value = selectedReport.value?.title || `${selectedReport.value?.prisonerName} - 评估报告`
|
||||||
|
|
||||||
|
// 监区信息兜底(报告未返回监区时,从罪犯档案补齐)
|
||||||
|
if (selectedReport.value?.prisonerId && !selectedReport.value.areaName) {
|
||||||
|
const prisoner = await PrisonerApi.get(selectedReport.value.prisonerId)
|
||||||
|
if (prisoner?.prisonAreaName) {
|
||||||
|
selectedReport.value.areaName = prisoner.prisonAreaName
|
||||||
|
selectedReport.value.areaId = prisoner.prisonAreaId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 加载维度配置
|
// 加载维度配置
|
||||||
if (selectedReport.value?.templateId) {
|
if (selectedReport.value?.templateId) {
|
||||||
try {
|
try {
|
||||||
@ -221,10 +211,35 @@ const handleSave = async () => {
|
|||||||
if (!selectedReport.value) return
|
if (!selectedReport.value) return
|
||||||
saving.value = true
|
saving.value = true
|
||||||
try {
|
try {
|
||||||
await ReportApi.updateReport(selectedReport.value)
|
const updateData = {
|
||||||
|
id: selectedReport.value.id,
|
||||||
|
prisonerId: selectedReport.value.prisonerId,
|
||||||
|
templateId: selectedReport.value.templateId,
|
||||||
|
evaluationType: selectedReport.value.evaluationType,
|
||||||
|
evaluationCycle: selectedReport.value.evaluationCycle,
|
||||||
|
evaluationDate: selectedReport.value.evaluationDate,
|
||||||
|
status: selectedReport.value.status,
|
||||||
|
riskLevel: selectedReport.value.riskLevel,
|
||||||
|
conclusion: selectedReport.value.conclusion,
|
||||||
|
suggestions: selectedReport.value.suggestions,
|
||||||
|
remark: selectedReport.value.remark,
|
||||||
|
areaId: selectedReport.value.areaId,
|
||||||
|
areaName: selectedReport.value.areaName,
|
||||||
|
evaluatorId: selectedReport.value.evaluatorId,
|
||||||
|
evaluatorName: selectedReport.value.evaluatorName
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!updateData.prisonerId || !updateData.templateId || !updateData.evaluationType || !updateData.evaluationCycle || !updateData.evaluationDate) {
|
||||||
|
message.error('报告缺少必填信息,请刷新后重试')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await ReportApi.updateReport(updateData)
|
||||||
for (const dimension of dimensionDataList.value) {
|
for (const dimension of dimensionDataList.value) {
|
||||||
if (dimension.id) {
|
if (dimension.id) {
|
||||||
await DimensionDataApi.updateDimensionData(dimension)
|
await DimensionDataApi.updateDimensionData(dimension)
|
||||||
|
} else {
|
||||||
|
await DimensionDataApi.createDimensionData(dimension)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
message.success('保存成功')
|
message.success('保存成功')
|
||||||
|
|||||||
@ -245,7 +245,7 @@ const handleDeleteDimension = async (id: number) => {
|
|||||||
await message.delConfirm()
|
await message.delConfirm()
|
||||||
await DimensionApi.deleteDimension(id)
|
await DimensionApi.deleteDimension(id)
|
||||||
message.success(t('common.delSuccess'))
|
message.success(t('common.delSuccess'))
|
||||||
getDimensionsByTemplateId(selectedTemplate.value?.id!)
|
getDimensionsByTemplateId(selectedTemplate.value?.id)
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -308,15 +308,13 @@ import PrisonerWorkbench from './PrisonerWorkbench.vue'
|
|||||||
import TransferForm from './TransferForm.vue'
|
import TransferForm from './TransferForm.vue'
|
||||||
import download from '@/utils/download'
|
import download from '@/utils/download'
|
||||||
import { PrisonerApi } from '@/api/prison/prisoner'
|
import { PrisonerApi } from '@/api/prison/prisoner'
|
||||||
import type { PrisonerVO, PrisonerCreateVO } from '@/api/prison/prisoner'
|
import type { PrisonerVO } from '@/api/prison/prisoner'
|
||||||
import { AreaApi, type AreaNode } from '@/api/prison/area'
|
import { AreaApi, type AreaNode } from '@/api/prison/area'
|
||||||
import { CellApi, type CellVO } from '@/api/prison/cell'
|
import { CellApi, type CellVO } from '@/api/prison/cell'
|
||||||
import { ref, reactive, onMounted } from 'vue'
|
import { ref, reactive, onMounted } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
|
||||||
|
|
||||||
defineOptions({ name: 'PrisonPrisoner' })
|
defineOptions({ name: 'PrisonPrisoner' })
|
||||||
|
|
||||||
const router = useRouter()
|
|
||||||
const { t } = useI18n() // 国际化
|
const { t } = useI18n() // 国际化
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
|
|
||||||
@ -328,18 +326,18 @@ const cellList = ref<CellVO[]>([]) // 监室列表
|
|||||||
const queryParams = reactive({
|
const queryParams = reactive({
|
||||||
pageNo: 1,
|
pageNo: 1,
|
||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
prisonerNo: undefined,
|
prisonerNo: undefined as string | undefined,
|
||||||
name: undefined,
|
name: undefined as string | undefined,
|
||||||
gender: undefined,
|
gender: undefined as number | undefined,
|
||||||
crime: undefined,
|
crime: undefined as string | undefined,
|
||||||
supervisionLevel: undefined,
|
supervisionLevel: undefined as number | undefined,
|
||||||
riskLevel: undefined,
|
riskLevel: undefined as number | undefined,
|
||||||
prisonAreaId: undefined,
|
prisonAreaId: undefined as number | undefined,
|
||||||
prisonCellId: undefined,
|
prisonCellId: undefined as number | undefined,
|
||||||
status: undefined,
|
status: undefined as number | undefined,
|
||||||
imprisonmentDate: [] as string[], // 日期范围数组
|
imprisonmentDate: [] as string[], // 日期范围数组
|
||||||
imprisonmentDateStart: undefined,
|
imprisonmentDateStart: undefined as string | undefined,
|
||||||
imprisonmentDateEnd: undefined
|
imprisonmentDateEnd: undefined as string | undefined
|
||||||
})
|
})
|
||||||
const queryFormRef = ref() // 搜索的表单
|
const queryFormRef = ref() // 搜索的表单
|
||||||
const exportLoading = ref(false) // 导出的加载中
|
const exportLoading = ref(false) // 导出的加载中
|
||||||
@ -455,7 +453,7 @@ const handleAreaChange = async (areaId: number) => {
|
|||||||
queryParams.prisonCellId = undefined
|
queryParams.prisonCellId = undefined
|
||||||
cellList.value = []
|
cellList.value = []
|
||||||
if (areaId) {
|
if (areaId) {
|
||||||
cellList.value = await CellApi.getCellPage({ areaId, pageNo: 1, pageSize: 200 }).then((res: PageResult<CellVO>) => res.list || [])
|
cellList.value = await CellApi.getCellPage({ areaId, pageNo: 1, pageSize: 200 }).then((res: { list: CellVO[] }) => res.list || [])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -21,7 +21,7 @@
|
|||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="问卷说明" prop="description">
|
<el-form-item label="问卷说明" prop="description">
|
||||||
<Editor v-model="formData.description" height="150px" />
|
<Editor v-model="formData.description" height="350px" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="封面图片" prop="coverImage">
|
<el-form-item label="封面图片" prop="coverImage">
|
||||||
<UploadImgs v-model="formData.coverImage" :limit="1" />
|
<UploadImgs v-model="formData.coverImage" :limit="1" />
|
||||||
@ -104,7 +104,12 @@ const open = async (type: string, id?: number) => {
|
|||||||
if (id) {
|
if (id) {
|
||||||
formLoading.value = true
|
formLoading.value = true
|
||||||
try {
|
try {
|
||||||
formData.value = await QuestionnaireApi.getQuestionnaire(id)
|
const data = await QuestionnaireApi.getQuestionnaire(id)
|
||||||
|
// 处理 coverImage 可能为 null 的情况
|
||||||
|
if (data.coverImage === null) {
|
||||||
|
data.coverImage = []
|
||||||
|
}
|
||||||
|
formData.value = data
|
||||||
} finally {
|
} finally {
|
||||||
formLoading.value = false
|
formLoading.value = false
|
||||||
}
|
}
|
||||||
|
|||||||
@ -91,6 +91,7 @@
|
|||||||
highlight-current-row
|
highlight-current-row
|
||||||
@current-change="handleCurrentChange"
|
@current-change="handleCurrentChange"
|
||||||
@selection-change="handleRowCheckboxChange"
|
@selection-change="handleRowCheckboxChange"
|
||||||
|
:scroll-x="true"
|
||||||
>
|
>
|
||||||
<el-table-column type="selection" width="55" />
|
<el-table-column type="selection" width="55" />
|
||||||
<el-table-column label="问卷ID" align="center" prop="id" width="80" />
|
<el-table-column label="问卷ID" align="center" prop="id" width="80" />
|
||||||
|
|||||||
@ -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
|
||||||
@ -97,6 +105,8 @@
|
|||||||
:stripe="true"
|
:stripe="true"
|
||||||
:show-overflow-tooltip="true"
|
:show-overflow-tooltip="true"
|
||||||
@selection-change="handleSelectionChange"
|
@selection-change="handleSelectionChange"
|
||||||
|
:scroll-x="true"
|
||||||
|
style="width: 100%; min-width: 1200px"
|
||||||
>
|
>
|
||||||
<el-table-column type="selection" width="55" align="center" />
|
<el-table-column type="selection" width="55" align="center" />
|
||||||
<el-table-column label="编号" align="center" prop="id" width="80" />
|
<el-table-column label="编号" align="center" prop="id" width="80" />
|
||||||
@ -170,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>
|
||||||
@ -178,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' })
|
||||||
|
|
||||||
@ -204,7 +225,7 @@ const queryParams = reactive<RiskPageReqVO>({
|
|||||||
const queryFormRef = ref()
|
const queryFormRef = ref()
|
||||||
|
|
||||||
// 字典数据
|
// 字典数据
|
||||||
const assessmentTypeOptions = getIntDictOptions(DICT_TYPE.PRISON_ASSESSMENT_TYPE)
|
const assessmentTypeOptions = getIntDictOptions(DICT_TYPE.PRISON_RISK_ASSESSMENT_TYPE)
|
||||||
const riskLevelOptions = getIntDictOptions(DICT_TYPE.PRISON_RISK_LEVEL)
|
const riskLevelOptions = getIntDictOptions(DICT_TYPE.PRISON_RISK_LEVEL)
|
||||||
|
|
||||||
/** 根据得分返回样式类名 */
|
/** 根据得分返回样式类名 */
|
||||||
@ -273,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()
|
||||||
|
|||||||
@ -29,7 +29,7 @@
|
|||||||
<el-form-item label="评估类型" prop="assessmentType">
|
<el-form-item label="评估类型" prop="assessmentType">
|
||||||
<el-select v-model="formData.assessmentType" placeholder="请选择评估类型">
|
<el-select v-model="formData.assessmentType" placeholder="请选择评估类型">
|
||||||
<el-option
|
<el-option
|
||||||
v-for="dict in getIntDictOptions(DICT_TYPE.PRISON_ASSESSMENT_TYPE)"
|
v-for="dict in getIntDictOptions(DICT_TYPE.PRISON_RISK_ASSESSMENT_TYPE)"
|
||||||
:key="dict.value"
|
:key="dict.value"
|
||||||
:label="dict.label"
|
:label="dict.label"
|
||||||
:value="dict.value"
|
:value="dict.value"
|
||||||
|
|||||||
@ -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
|
||||||
@ -110,6 +118,7 @@
|
|||||||
v-loading="loading"
|
v-loading="loading"
|
||||||
:data="list"
|
:data="list"
|
||||||
@selection-change="handleRowCheckboxChange"
|
@selection-change="handleRowCheckboxChange"
|
||||||
|
:scroll-x="true"
|
||||||
>
|
>
|
||||||
<el-table-column type="selection" width="55" />
|
<el-table-column type="selection" width="55" />
|
||||||
<el-table-column label="评估ID" align="center" prop="id" width="80" />
|
<el-table-column label="评估ID" align="center" prop="id" width="80" />
|
||||||
@ -117,7 +126,7 @@
|
|||||||
<el-table-column label="罪犯姓名" align="center" prop="prisonerName" width="100" />
|
<el-table-column label="罪犯姓名" align="center" prop="prisonerName" width="100" />
|
||||||
<el-table-column label="评估类型" align="center" prop="assessmentType" width="100">
|
<el-table-column label="评估类型" align="center" prop="assessmentType" width="100">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<dict-tag :type="DICT_TYPE.PRISON_ASSESSMENT_TYPE" :value="scope.row.assessmentType" />
|
<dict-tag :type="DICT_TYPE.PRISON_RISK_ASSESSMENT_TYPE" :value="scope.row.assessmentType" />
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="评估日期" align="center" prop="assessmentDate" width="120">
|
<el-table-column label="评估日期" align="center" prop="assessmentDate" width="120">
|
||||||
@ -177,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>
|
||||||
@ -185,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' })
|
||||||
|
|
||||||
@ -207,7 +227,7 @@ const queryFormRef = ref()
|
|||||||
const exportLoading = ref(false)
|
const exportLoading = ref(false)
|
||||||
|
|
||||||
// 使用字典获取选项
|
// 使用字典获取选项
|
||||||
const assessmentTypeOptions = getIntDictOptions(DICT_TYPE.PRISON_ASSESSMENT_TYPE)
|
const assessmentTypeOptions = getIntDictOptions(DICT_TYPE.PRISON_RISK_ASSESSMENT_TYPE)
|
||||||
const riskLevelOptions = getIntDictOptions(DICT_TYPE.PRISON_RISK_LEVEL)
|
const riskLevelOptions = getIntDictOptions(DICT_TYPE.PRISON_RISK_LEVEL)
|
||||||
const statusOptions = getIntDictOptions(DICT_TYPE.PRISON_SCORE_STATUS)
|
const statusOptions = getIntDictOptions(DICT_TYPE.PRISON_SCORE_STATUS)
|
||||||
|
|
||||||
@ -280,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()
|
||||||
|
|||||||
@ -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()
|
||||||
|
|||||||
@ -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()
|
||||||
|
|||||||
@ -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()
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user