add 巡检表单

pull/853/head
wersd 2025-11-13 20:04:38 +08:00
parent aa7132c91a
commit 42f655c129
10 changed files with 1377 additions and 15 deletions

View File

@ -5,11 +5,11 @@ export interface ProjectInspectionDataVO {
id: number // 主键ID
configType: number // 巡检配置类型
name: string // 名称
isRequired: boolean // 是否必填0-否1-是
isRequired: number // 是否必填0-否1-是
type: number // 类型1-文本,2-数字,3-单选,4-多选,5-日期,6-图片,7-文件
options: string // 选项内容
sortOrder: number // 排序序号
status: boolean // 状态0-停用1-启用
status: number // 状态0-停用1-启用
remark: string // 备注
}
@ -44,7 +44,7 @@ export interface ProjectInspectionDataVO {
return await request.download({ url: `/crm/project-inspection-data/export-excel`, params })
}
// 获取项目巡检内容列表
export const getProjectInspectionDataList = async (type: number) => {
return await request.get({ url: `/crm/project-inspection-data/list?configType=` + type })
export const getProjectInspectionDataList = async (types: any[]) => {
return await request.get({ url: `/crm/project-inspection-data/list`, params: { types: types.join(',') }})
}

View File

@ -2,15 +2,17 @@ import request from '@/config/axios'
// 项目巡检配置 VO
export interface ProjectInspectionConfigVO {
id?: number // 主键ID
projectId: number // 项目ID
configId: number // 配置ID
configType?: number // 配置类型
configName: string // 配置名称
frequency: string // 巡检频率
managementParty: string // 管理方
contactPerson: string // 联系人
contactPhone: string // 联系电话
responsiblePersonId: string // 负责人
status: boolean // 状态0-停用1-启用
status: number // 状态0-停用1-启用
remark: string // 备注
}

View File

@ -0,0 +1,43 @@
import request from '@/config/axios'
// 巡检记录 VO
export interface ProjectInspectionRecordVO {
id: number // 主键ID
projectId: number // 项目ID
inspectionDate: Date // 巡检日期
inspector: number // 巡检人
overallStatus: number // 总体状态
summary: string // 巡检总结
remark: string // 备注
status: number // 状态
}
// 查询巡检记录分页
export const getProjectInspectionRecordPage = async (params: any) => {
return await request.get({ url: `/crm/project-inspection-record/page`, params })
}
// 查询巡检记录详情
export const getProjectInspectionRecord = async (id: number) => {
return await request.get({ url: `/crm/project-inspection-record/get?id=` + id })
}
// 新增巡检记录
export const createProjectInspectionRecord = async (data: ProjectInspectionRecordVO) => {
return await request.post({ url: `/crm/project-inspection-record/create`, data })
}
// 修改巡检记录
export const updateProjectInspectionRecord = async (data: ProjectInspectionRecordVO) => {
return await request.put({ url: `/crm/project-inspection-record/update`, data })
}
// 删除巡检记录
export const deleteProjectInspectionRecord = async (id: number) => {
return await request.delete({ url: `/crm/project-inspection-record/delete?id=` + id })
}
// 导出巡检记录 Excel
export const exportProjectInspectionRecord = async (params) => {
return await request.download({ url: `/crm/project-inspection-record/export-excel`, params })
}

View File

@ -231,6 +231,8 @@ export enum DICT_TYPE {
CRM_PROJECT_SETTLEMENT_STATUS = 'crm_project_settlement_status', // CRM 项目结算状态
CRM_PROJECT_INSPECTION_CHECKPOINT = 'crm_project_inspection_checkpoint', // CRM 项目巡检配置
CRM_PROJECT_INSPECTION_DATA_TYPE = 'crm_project_inspection_data_type', // CRM 项目巡检内容类型
CRM_PROJECT_INSPECTION_RECORD_OVERALL_STATUS = 'crm_project_inspection_record_overall_status', // CRM 项目巡检记录总体状态
CRM_PROJECT_INSPECTION_RECORD_STATUS = 'crm_project_inspection_record_status', // CRM 项目巡检记录状态
// ========== ERP - 企业资源计划模块 ==========

View File

@ -0,0 +1,128 @@
<template>
<Dialog :title="dialogTitle" v-model="dialogVisible">
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="100px"
v-loading="formLoading"
>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="配置名称" prop="configName">
<el-input v-model="formData.configName" placeholder="请输入配置名称" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="显示顺序" prop="sort">
<el-input-number
v-model="formData.sort"
:min="0"
placeholder="请输入显示顺序"
class="!w-full"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="状态" prop="status">
<el-radio-group v-model="formData.status">
<el-radio
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
:key="dict.value"
:label="dict.value"
>
{{ dict.label }}
</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<el-button @click="submitForm" type="primary" :disabled="formLoading"> </el-button>
<el-button @click="dialogVisible = false"> </el-button>
</template>
</Dialog>
</template>
<script setup lang="ts">
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
import * as ProjectInspectionCheckpointApi from '@/api/crm/project/inspection/checkpoint'
/** 检查点配置 表单 */
defineOptions({ name: 'ProjectInspectionCheckpointForm' })
const { t } = useI18n() //
const message = useMessage() //
const dialogVisible = ref(false) //
const dialogTitle = ref('') //
const formLoading = ref(false) // 12
const formType = ref('') // create - update -
const formData = ref({
id: undefined,
configName: undefined,
sort: undefined,
status: undefined,
})
const formRules = reactive({
configName: [{ required: true, message: '配置名称不能为空', trigger: 'blur' }],
sort: [{ required: true, message: '显示顺序不能为空', trigger: 'blur' }],
status: [{ required: true, message: '状态不能为空', trigger: 'blur' }],
})
const formRef = ref() // Ref
/** 打开弹窗 */
const open = async (type: string, id?: number) => {
dialogVisible.value = true
dialogTitle.value = t('action.' + type)
formType.value = type
resetForm()
//
if (id) {
formLoading.value = true
try {
formData.value = await ProjectInspectionCheckpointApi.getProjectInspectionCheckpoint(id)
} finally {
formLoading.value = false
}
}
}
defineExpose({ open }) // open
/** 提交表单 */
const emit = defineEmits(['success']) // success
const submitForm = async () => {
//
await formRef.value.validate()
//
formLoading.value = true
try {
const data = formData.value as unknown as ProjectInspectionCheckpointApi.ProjectInspectionCheckpointVO
if (formType.value === 'create') {
await ProjectInspectionCheckpointApi.createProjectInspectionCheckpoint(data)
message.success(t('common.createSuccess'))
} else {
await ProjectInspectionCheckpointApi.updateProjectInspectionCheckpoint(data)
message.success(t('common.updateSuccess'))
}
dialogVisible.value = false
//
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
id: undefined,
configName: undefined,
sort: undefined,
status: undefined,
}
formRef.value?.resetFields()
}
</script>

View File

@ -16,7 +16,7 @@
class="!w-full"
>
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.CRM_PROJECT_INSPECTION_DATA_TYPE)"
v-for="dict in getIntDictOptions(DICT_TYPE.CRM_PROJECT_INSPECTION_CHECKPOINT)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
@ -57,13 +57,41 @@
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<!-- 选项内容只有单选或多选时才显示 -->
<el-row :gutter="20" v-if="formData.type === 3 || formData.type === 4">
<el-col :span="24">
<el-form-item label="选项内容" prop="options">
<el-input
v-model="formData.options"
placeholder="请输入选项内容(多个选项用逗号分隔)"
/>
<div class="options-container">
<div
v-for="(_, index) in optionsList"
:key="index"
class="option-item"
>
<el-input
v-model="optionsList[index]"
:placeholder="`请输入选项 ${index + 1}`"
class="option-input"
/>
<el-button
type="danger"
:icon="Delete"
circle
size="small"
@click="removeOption(index)"
:disabled="optionsList.length <= 1"
class="option-delete-btn"
/>
</div>
<el-button
type="primary"
:icon="Plus"
plain
@click="addOption"
class="add-option-btn"
>
添加选项
</el-button>
</div>
</el-form-item>
</el-col>
</el-row>
@ -115,6 +143,7 @@
<script setup lang="ts">
import { getIntDictOptions, getBoolDictOptions, DICT_TYPE } from '@/utils/dict'
import * as ProjectInspectionDataApi from '@/api/crm/project/inspection/data'
import { Plus, Delete } from '@element-plus/icons-vue'
/** 项目巡检内容 表单 */
defineOptions({ name: 'ProjectInspectionDataForm' })
@ -126,7 +155,7 @@ const dialogVisible = ref(false) // 弹窗的是否展示
const dialogTitle = ref('') //
const formLoading = ref(false) // 12
const formType = ref('') // create - update -
const formData = ref({
const formData = ref<Partial<ProjectInspectionDataApi.ProjectInspectionDataVO>>({
id: undefined,
configType: undefined,
name: undefined,
@ -142,11 +171,121 @@ const formRules = reactive({
name: [{ required: true, message: '管理规则不能为空', trigger: 'blur' }],
isRequired: [{ required: true, message: '是否必填不能为空', trigger: 'blur' }],
type: [{ required: true, message: '类型不能为空', trigger: 'change' }],
options: [
{
validator: (_rule: any, _value: any, callback: any) => {
if (formData.value.type === 3 || formData.value.type === 4) {
if (!optionsList.value || optionsList.value.length === 0) {
callback(new Error('选项内容不能为空'))
} else if (optionsList.value.some((opt: string) => !opt || !opt.trim())) {
callback(new Error('选项内容不能为空'))
} else {
callback()
}
} else {
callback()
}
},
trigger: 'blur'
}
],
sortOrder: [{ required: true, message: '排序序号不能为空', trigger: 'blur' }],
status: [{ required: true, message: '状态不能为空', trigger: 'blur' }],
})
const formRef = ref() // Ref
//
const optionsList = ref<string[]>([''])
/** 解析选项字符串为数组 */
const parseOptions = (options: string | undefined): string[] => {
if (!options) return ['']
//
if (Array.isArray(options)) {
return options.length > 0 ? options : ['']
}
//
if (typeof options === 'string') {
// JSON
try {
const parsed = JSON.parse(options)
if (Array.isArray(parsed)) {
return parsed.length > 0 ? parsed : ['']
}
} catch (e) {
// JSON
}
//
const items = options.split(',').map(opt => opt.trim()).filter(opt => opt)
return items.length > 0 ? items : ['']
}
return ['']
}
/** 将选项数组转换为字符串 */
const optionsToString = (options: string[]): string => {
const filtered = options.filter(opt => opt && opt.trim())
return filtered.length > 0 ? filtered.join(',') : ''
}
/** 添加选项 */
const addOption = () => {
optionsList.value.push('')
}
/** 删除选项 */
const removeOption = (index: number) => {
if (optionsList.value.length > 1) {
optionsList.value.splice(index, 1)
}
}
// formData
watch(
optionsList,
(newVal) => {
const optionsStr = optionsToString(newVal)
// API options string
formData.value.options = optionsStr
},
{ deep: true }
)
//
watch(
() => formData.value.type,
(newType) => {
if (newType === 3 || newType === 4) {
//
if (!formData.value.options || optionsList.value.length === 0) {
optionsList.value = ['']
} else {
//
optionsList.value = parseOptions(formData.value.options)
}
} else {
// /
optionsList.value = ['']
formData.value.options = ''
}
}
)
// formData.options
watch(
() => formData.value.options,
(newOptions) => {
if (formData.value.type === 3 || formData.value.type === 4) {
optionsList.value = parseOptions(newOptions)
}
},
{ immediate: true }
)
/** 打开弹窗 */
const open = async (type: string, id?: number) => {
dialogVisible.value = true
@ -157,7 +296,12 @@ const open = async (type: string, id?: number) => {
if (id) {
formLoading.value = true
try {
formData.value = await ProjectInspectionDataApi.getProjectInspectionData(id)
const data = await ProjectInspectionDataApi.getProjectInspectionData(id)
formData.value = data
//
if (data.type === 3 || data.type === 4) {
optionsList.value = parseOptions(data.options)
}
} finally {
formLoading.value = false
}
@ -168,6 +312,16 @@ defineExpose({ open }) // 提供 open 方法,用于打开弹窗
/** 提交表单 */
const emit = defineEmits(['success']) // success
const submitForm = async () => {
//
if (formData.value.type === 3 || formData.value.type === 4) {
const optionsStr = optionsToString(optionsList.value)
// API options string
formData.value.options = optionsStr
} else {
// /
formData.value.options = ''
}
//
await formRef.value.validate()
//
@ -202,6 +356,33 @@ const resetForm = () => {
status: undefined,
remark: undefined,
}
optionsList.value = ['']
formRef.value?.resetFields()
}
</script>
</script>
<style scoped lang="scss">
.options-container {
width: 100%;
}
.option-item {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 12px;
.option-input {
flex: 1;
}
.option-delete-btn {
flex-shrink: 0;
}
}
.add-option-btn {
width: 100%;
margin-top: 8px;
}
</style>

View File

@ -104,7 +104,6 @@
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
<el-table-column label="主键ID" align="center" prop="id" />
<el-table-column label="巡检配置类型" align="center" prop="configType">
<template #default="scope">
<dict-tag :type="DICT_TYPE.CRM_PROJECT_INSPECTION_CHECKPOINT" :value="scope.row.configType" />

View File

@ -0,0 +1,217 @@
<template>
<Dialog :title="dialogTitle" v-model="dialogVisible">
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="100px"
v-loading="formLoading"
>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="项目名称" prop="projectId">
<el-select
v-model="formData.projectId"
placeholder="请选择项目名称"
clearable
filterable
class="!w-full"
>
<el-option
v-for="item in projectList"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="巡检日期" prop="inspectionDate">
<el-date-picker
v-model="formData.inspectionDate"
type="date"
value-format="YYYY-MM-DD"
placeholder="选择巡检日期"
class="!w-full"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="巡检人" prop="inspector">
<el-select
v-model="formData.inspector"
placeholder="请选择巡检人"
clearable
filterable
class="!w-full"
>
<el-option
v-for="item in userList"
:key="item.id"
:label="item.nickname"
:value="item.id"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="状态" prop="status">
<el-radio-group v-model="formData.status">
<el-radio
v-for="dict in getIntDictOptions(DICT_TYPE.CRM_PROJECT_INSPECTION_RECORD_STATUS)"
:key="dict.value"
:label="dict.value"
>
{{ dict.label }}
</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="24">
<el-form-item label="总体状态" prop="overallStatus">
<el-radio-group v-model="formData.overallStatus">
<el-radio
v-for="dict in getIntDictOptions(DICT_TYPE.CRM_PROJECT_INSPECTION_RECORD_OVERALL_STATUS)"
:key="dict.value"
:label="dict.value"
>
{{ dict.label }}
</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="24">
<el-form-item label="巡检总结" prop="summary">
<el-input
v-model="formData.summary"
type="textarea"
:rows="3"
placeholder="请输入巡检总结"
resize="both"
/>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="24">
<el-form-item label="备注" prop="remark">
<el-input
v-model="formData.remark"
type="textarea"
:rows="3"
placeholder="请输入备注"
resize="both"
/>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<el-button @click="submitForm" type="primary" :disabled="formLoading"> </el-button>
<el-button @click="dialogVisible = false"> </el-button>
</template>
</Dialog>
</template>
<script setup lang="ts">
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
import * as ProjectInspectionRecordApi from '@/api/crm/project/inspection/record'
import * as ProjectApi from '@/api/crm/project'
import * as UserApi from '@/api/system/user'
/** 巡检记录 表单 */
defineOptions({ name: 'ProjectInspectionRecordForm' })
const { t } = useI18n() //
const message = useMessage() //
const dialogVisible = ref(false) //
const dialogTitle = ref('') //
const formLoading = ref(false) // 12
const formType = ref('') // create - update -
const formData = ref({
id: undefined,
projectId: undefined,
inspectionDate: undefined,
inspector: undefined,
overallStatus: undefined,
summary: undefined,
remark: undefined,
status: undefined,
})
const formRules = reactive({
projectId: [{ required: true, message: '项目名称不能为空', trigger: 'change' }],
inspectionDate: [{ required: true, message: '巡检日期不能为空', trigger: 'blur' }],
inspector: [{ required: true, message: '巡检人不能为空', trigger: 'change' }],
status: [{ required: true, message: '状态不能为空', trigger: 'blur' }],
})
const formRef = ref() // Ref
const projectList = ref<ProjectApi.ProjectVO[]>([]) //
const userList = ref<UserApi.UserVO[]>([]) //
/** 打开弹窗 */
const open = async (type: string, id?: number) => {
dialogVisible.value = true
dialogTitle.value = t('action.' + type)
formType.value = type
resetForm()
//
projectList.value = await ProjectApi.getProjectSimpleList()
userList.value = await UserApi.getSimpleUserList()
//
if (id) {
formLoading.value = true
try {
formData.value = await ProjectInspectionRecordApi.getProjectInspectionRecord(id)
} finally {
formLoading.value = false
}
}
}
defineExpose({ open }) // open
/** 提交表单 */
const emit = defineEmits(['success']) // success
const submitForm = async () => {
//
await formRef.value.validate()
//
formLoading.value = true
try {
const data = formData.value as unknown as ProjectInspectionRecordApi.ProjectInspectionRecordVO
if (formType.value === 'create') {
await ProjectInspectionRecordApi.createProjectInspectionRecord(data)
message.success(t('common.createSuccess'))
} else {
await ProjectInspectionRecordApi.updateProjectInspectionRecord(data)
message.success(t('common.updateSuccess'))
}
dialogVisible.value = false
//
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
id: undefined,
projectId: undefined,
inspectionDate: undefined,
inspector: undefined,
overallStatus: undefined,
summary: undefined,
remark: undefined,
status: undefined,
}
formRef.value?.resetFields()
}
</script>

View File

@ -0,0 +1,390 @@
<template>
<Dialog :title="dialogTitle" v-model="dialogVisible" :width="800" :scroll="true" :max-height="600">
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="120px"
v-loading="formLoading"
>
<el-form-item label="项目名称" prop="projectId">
<el-input v-model="projectName" disabled />
</el-form-item>
<el-form-item label="巡检日期" prop="inspectionDate">
<el-input v-model="inspectionDate" disabled />
</el-form-item>
<el-form-item label="巡检人" prop="inspector">
<el-input v-model="inspectorName" disabled />
</el-form-item>
<!-- 按配置类型分组显示巡检内容字段 -->
<template v-for="group in groupedInspectionData" :key="group.configType">
<!-- 分组标题 -->
<el-divider>
<span style="font-weight: bold; font-size: 16px;">
{{ getConfigTypeName(group.configType) }}
</span>
</el-divider>
<!-- 分组下的表单项 -->
<template v-for="item in group.items" :key="item.id">
<el-form-item
:label="item.name"
:prop="`data_${item.id}`"
:rules="getFieldRules(item)"
>
<!-- 文本类型 -->
<el-input
v-if="item.type === 1"
v-model="formData[`data_${item.id}`]"
:placeholder="`请输入${item.name}`"
clearable
/>
<!-- 数字类型 -->
<el-input-number
v-else-if="item.type === 2"
v-model="formData[`data_${item.id}`]"
:placeholder="`请输入${item.name}`"
class="!w-full"
:min="0"
:precision="2"
/>
<!-- 单选类型 -->
<el-radio-group
v-else-if="item.type === 3"
v-model="formData[`data_${item.id}`]"
class="radio-group-flex"
>
<el-radio
v-for="option in getOptions(item.options)"
:key="option"
:label="option"
class="radio-item"
>
{{ option }}
</el-radio>
</el-radio-group>
<!-- 多选类型 -->
<el-checkbox-group
v-else-if="item.type === 4"
v-model="formData[`data_${item.id}`]"
class="checkbox-group-flex"
>
<el-checkbox
v-for="option in getOptions(item.options)"
:key="option"
:label="option"
class="checkbox-item"
>
{{ option }}
</el-checkbox>
</el-checkbox-group>
<!-- 日期类型 -->
<el-date-picker
v-else-if="item.type === 5"
v-model="formData[`data_${item.id}`]"
type="date"
value-format="YYYY-MM-DD"
:placeholder="`请选择${item.name}`"
class="!w-full"
/>
<!-- 图片类型 -->
<UploadImg
v-else-if="item.type === 6"
v-model="formData[`data_${item.id}`]"
/>
<!-- 文件类型 -->
<UploadFile
v-else-if="item.type === 7"
v-model="formData[`data_${item.id}`]"
:limit="1"
:file-size="20"
:file-type="['pdf', 'doc', 'docx', 'xls', 'xlsx', 'txt']"
/>
</el-form-item>
</template>
</template>
</el-form>
<template #footer>
<el-button @click="submitForm" type="primary" :disabled="formLoading"> </el-button>
<el-button @click="dialogVisible = false"> </el-button>
</template>
</Dialog>
</template>
<script setup lang="ts">
import * as ProjectInspectionRecordApi from '@/api/crm/project/inspection/record'
import * as ProjectInspectionDataApi from '@/api/crm/project/inspection/data'
import * as ProjectApi from '@/api/crm/project'
import * as UserApi from '@/api/system/user'
import UploadImg from '@/components/UploadFile/src/UploadImg.vue'
import UploadFile from '@/components/UploadFile/src/UploadFile.vue'
import { getDictLabel, getIntDictOptions, DICT_TYPE } from '@/utils/dict'
import { useUserStore } from '@/store/modules/user'
/** 巡检记录填报 表单 */
defineOptions({ name: 'ProjectInspectionRecordUploadForm' })
const message = useMessage() //
const userStore = useUserStore() // store
const dialogVisible = ref(false) //
const dialogTitle = ref('巡检填报') //
const formLoading = ref(false) //
const formRef = ref() // Ref
const inspectionDataList = ref<ProjectInspectionDataApi.ProjectInspectionDataVO[]>([]) //
const projectList = ref<ProjectApi.ProjectVO[]>([]) //
const userList = ref<UserApi.UserVO[]>([]) //
const projectName = ref('') //
const inspectionDate = ref('') //
const inspectorName = ref('') //
const recordId = ref<number | undefined>(undefined) // ID
const originalRecord = ref<ProjectInspectionRecordApi.ProjectInspectionRecordVO | null>(null) //
const formData = ref<Record<string, any>>({}) //
const formRules = ref<Record<string, any>>({}) //
/** 按配置类型分组的巡检数据 */
const groupedInspectionData = computed(() => {
const groups: Record<number, ProjectInspectionDataApi.ProjectInspectionDataVO[]> = {}
// configType
inspectionDataList.value.forEach((item) => {
const configType = item.configType
if (!groups[configType]) {
groups[configType] = []
}
groups[configType].push(item)
})
//
const dictOptions = getIntDictOptions(DICT_TYPE.CRM_PROJECT_INSPECTION_CHECKPOINT)
// configType
const configTypeOrderMap = new Map<number, number>()
dictOptions.forEach((dict, index) => {
configTypeOrderMap.set(dict.value, index)
})
//
const groupedArray = Object.keys(groups).map(configType => ({
configType: Number(configType),
items: groups[Number(configType)].sort((a, b) => (a.sortOrder || 0) - (b.sortOrder || 0))
}))
//
return groupedArray.sort((a, b) => {
const orderA = configTypeOrderMap.get(a.configType)
const orderB = configTypeOrderMap.get(b.configType)
//
if (orderA !== undefined && orderB !== undefined) {
return orderA - orderB
}
//
if (orderA !== undefined) {
return -1
}
if (orderB !== undefined) {
return 1
}
// configType
return a.configType - b.configType
})
})
/** 获取配置类型名称 */
const getConfigTypeName = (configType: number): string => {
const name = getDictLabel(DICT_TYPE.CRM_PROJECT_INSPECTION_CHECKPOINT, configType)
return name || `配置类型 ${configType}`
}
/** 解析选项内容 */
const getOptions = (options: string | string[] | undefined): string[] => {
if (!options) return []
//
if (Array.isArray(options)) {
return options.filter(opt => opt !== null && opt !== undefined && opt !== '')
}
//
if (typeof options === 'string') {
// JSON '["1","2","3"]'
try {
const parsed = JSON.parse(options)
if (Array.isArray(parsed)) {
return parsed.map(opt => String(opt)).filter(opt => opt !== '')
}
} catch (e) {
// JSON
}
//
return options.split(',').map(opt => opt.trim()).filter(opt => opt !== '')
}
return []
}
/** 获取字段验证规则 */
const getFieldRules = (item: ProjectInspectionDataApi.ProjectInspectionDataVO) => {
const rules: any[] = []
if (item.isRequired) {
rules.push({
required: true,
message: `${item.name}不能为空`,
trigger: item.type === 3 || item.type === 4 ? 'change' : 'blur'
})
}
return rules
}
/** 打开弹窗 */
const open = async (
record: ProjectInspectionRecordApi.ProjectInspectionRecordVO,
dataList: ProjectInspectionDataApi.ProjectInspectionDataVO[]
) => {
dialogVisible.value = true
formLoading.value = true
try {
//
projectList.value = await ProjectApi.getProjectSimpleList()
userList.value = await UserApi.getSimpleUserList()
//
originalRecord.value = record
recordId.value = record.id
const project = projectList.value.find(p => p.id === record.projectId)
projectName.value = project?.name || ''
//
inspectorName.value = userStore.user.nickname || '当前用户'
//
if (record.inspectionDate) {
const date = new Date(record.inspectionDate)
inspectionDate.value = date.toISOString().split('T')[0]
}
//
inspectionDataList.value = dataList || []
//
formData.value = {}
formRules.value = {}
inspectionDataList.value.forEach((item) => {
const fieldKey = `data_${item.id}`
//
if (item.type === 4) {
//
formData.value[fieldKey] = []
} else {
formData.value[fieldKey] = undefined
}
//
formRules.value[fieldKey] = getFieldRules(item)
})
} finally {
formLoading.value = false
}
}
defineExpose({ open }) // open
/** 提交表单 */
const emit = defineEmits(['success']) // success
const submitForm = async () => {
//
await formRef.value.validate()
//
formLoading.value = true
try {
if (!recordId.value || !originalRecord.value) {
message.error('记录信息不完整')
return
}
//
const summaryData: Record<number, Record<number, any>> = {}
//
groupedInspectionData.value.forEach((group) => {
const groupData: Record<number, any> = {}
group.items.forEach((item) => {
const fieldKey = `data_${item.id}`
const value = formData.value[fieldKey]
//
if (item.type === 4 && Array.isArray(value)) {
//
if (value.length > 0) {
groupData[item.id] = value.join(',')
}
} else if (value !== undefined && value !== null && value !== '') {
//
groupData[item.id] = value
}
})
// summaryData
if (Object.keys(groupData).length > 0) {
summaryData[group.configType] = groupData
}
})
//
// status API boolean使
const updateData: any = {
id: recordId.value,
projectId: originalRecord.value.projectId,
inspectionDate: originalRecord.value.inspectionDate,
inspector: originalRecord.value.inspector,
overallStatus: originalRecord.value.overallStatus,
summary: JSON.stringify(summaryData), // JSON summary
remark: originalRecord.value.remark || '',
status: 2 // 2
}
// API
await ProjectInspectionRecordApi.updateProjectInspectionRecord(updateData)
message.success('填报成功')
dialogVisible.value = false
//
emit('success')
} catch (error) {
console.error('提交失败:', error)
message.error('提交失败,请稍后重试')
} finally {
formLoading.value = false
}
}
</script>
<style scoped lang="scss">
.radio-group-flex,
.checkbox-group-flex {
display: flex;
flex-wrap: wrap;
gap: 16px;
align-items: center;
}
:deep(.el-radio),
:deep(.el-checkbox) {
margin-right: 0 !important;
white-space: nowrap;
.el-radio__label,
.el-checkbox__label {
padding-left: 8px;
font-size: 14px;
}
}
</style>

View File

@ -0,0 +1,400 @@
<template>
<ContentWrap>
<!-- 搜索工作栏 -->
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="68px"
>
<el-form-item label="项目名称" prop="projectId">
<el-select
v-model="queryParams.projectId"
placeholder="请选择项目名称"
clearable
filterable
@keyup.enter="handleQuery"
class="!w-240px"
>
<el-option
v-for="item in projectList"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item label="巡检日期" prop="inspectionDate">
<el-date-picker
v-model="queryParams.inspectionDate"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-220px"
/>
</el-form-item>
<el-form-item label="巡检人" prop="inspector">
<el-select
v-model="queryParams.inspector"
placeholder="请选择巡检人"
clearable
filterable
@keyup.enter="handleQuery"
class="!w-240px"
>
<el-option
v-for="item in userList"
:key="item.id"
:label="item.nickname"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item label="总体状态" prop="overallStatus">
<el-select
v-model="queryParams.overallStatus"
placeholder="请选择总体状态"
clearable
class="!w-240px"
>
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.CRM_PROJECT_INSPECTION_RECORD_OVERALL_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select
v-model="queryParams.status"
placeholder="请选择状态"
clearable
class="!w-240px"
>
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.CRM_PROJECT_INSPECTION_RECORD_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
<el-button
type="primary"
plain
@click="openForm('create')"
v-hasPermi="['crm:project-inspection-record:create']"
>
<Icon icon="ep:plus" class="mr-5px" /> 新增
</el-button>
<el-button
type="success"
plain
@click="handleExport"
:loading="exportLoading"
v-hasPermi="['crm:project-inspection-record:export']"
>
<Icon icon="ep:download" class="mr-5px" /> 导出
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
<el-table-column label="项目名称" align="center" prop="projectId">
<template #default="scope">
{{ getProjectName(scope.row.projectId) }}
</template>
</el-table-column>
<el-table-column
label="巡检日期"
align="center"
prop="inspectionDate"
:formatter="dateFormatter2"
width="120"
/>
<el-table-column label="巡检人" align="center" prop="inspector">
<template #default="scope">
{{ getUserName(scope.row.inspector) }}
</template>
</el-table-column>
<el-table-column label="总体状态" align="center" prop="overallStatus">
<template #default="scope">
<dict-tag :type="DICT_TYPE.CRM_PROJECT_INSPECTION_RECORD_OVERALL_STATUS" :value="scope.row.overallStatus" />
</template>
</el-table-column>
<el-table-column label="备注" align="center" prop="remark" />
<el-table-column label="状态" align="center" prop="status">
<template #default="scope">
<dict-tag :type="DICT_TYPE.CRM_PROJECT_INSPECTION_RECORD_STATUS" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column label="操作" align="center" min-width="120px">
<template #default="scope">
<el-button
v-if="canShowUploadButton(scope.row)"
link
type="success"
@click="handleUpload(scope.row)"
>
填报
</el-button>
<el-button
link
type="primary"
@click="openForm('update', scope.row.id)"
v-hasPermi="['crm:project-inspection-record:update']"
>
编辑
</el-button>
<el-button
link
type="danger"
@click="handleDelete(scope.row.id)"
v-hasPermi="['crm:project-inspection-record:delete']"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
<!-- 表单弹窗添加/修改 -->
<ProjectInspectionRecordForm ref="formRef" @success="getList" />
<!-- 填报表单弹窗 -->
<ProjectInspectionRecordUploadForm ref="uploadFormRef" @success="getList" />
</template>
<script setup lang="ts">
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
import { dateFormatter2 } from '@/utils/formatTime'
import download from '@/utils/download'
import * as ProjectInspectionRecordApi from '@/api/crm/project/inspection/record'
import * as ProjectInspectionConfigApi from '@/api/crm/project/inspection'
import * as ProjectInspectionDataApi from '@/api/crm/project/inspection/data'
import * as ProjectApi from '@/api/crm/project'
import * as UserApi from '@/api/system/user'
import { useUserStore } from '@/store/modules/user'
import ProjectInspectionRecordForm from './ProjectInspectionRecordForm.vue'
import ProjectInspectionRecordUploadForm from './ProjectInspectionRecordUploadForm.vue'
/** 巡检记录 列表 */
defineOptions({ name: 'CrmProjectInspectionRecord' })
const message = useMessage() //
const { t } = useI18n() //
const loading = ref(true) //
const list = ref<ProjectInspectionRecordApi.ProjectInspectionRecordVO[]>([]) //
const total = ref(0) //
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
projectId: undefined,
inspectionDate: [],
inspector: undefined,
overallStatus: undefined,
status: undefined,
})
const queryFormRef = ref() //
const exportLoading = ref(false) //
const projectList = ref<ProjectApi.ProjectVO[]>([]) //
const userList = ref<UserApi.UserVO[]>([]) //
const userStore = useUserStore() // store
const uploadFormRef = ref() // Ref
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await ProjectInspectionRecordApi.getProjectInspectionRecordPage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
/** 添加/修改操作 */
const formRef = ref()
const openForm = (type: string, id?: number) => {
formRef.value.open(type, id)
}
/** 删除按钮操作 */
const handleDelete = async (id: number) => {
try {
//
await message.delConfirm()
//
await ProjectInspectionRecordApi.deleteProjectInspectionRecord(id)
message.success(t('common.delSuccess'))
//
await getList()
} catch {}
}
/** 导出按钮操作 */
const handleExport = async () => {
try {
//
await message.exportConfirm()
//
exportLoading.value = true
const data = await ProjectInspectionRecordApi.exportProjectInspectionRecord(queryParams)
download.excel(data, '巡检记录.xls')
} catch {
} finally {
exportLoading.value = false
}
}
/** 获取项目名称 */
const getProjectName = (projectId: number | undefined) => {
if (!projectId) return ''
const project = projectList.value.find(item => item.id === projectId)
return project?.name || ''
}
/** 获取用户名称 */
const getUserName = (userId: number | string | undefined) => {
if (!userId && userId !== 0) return ''
// inspector
const userIdNum = typeof userId === 'string' ? Number(userId) : userId
// NaN
if (isNaN(userIdNum as number)) return String(userId)
const user = userList.value.find(item => item.id === userIdNum)
return user?.nickname || String(userId)
}
/** 判断是否为管理员(有所有权限或超级管理员角色) */
const isAdmin = () => {
const allPermission = '*:*:*'
const superAdminRole = 'super_admin'
const permissions = userStore.permissions
const roles = userStore.roles || []
//
if (permissions && permissions.has(allPermission)) {
return true
}
//
if (roles.includes(superAdminRole)) {
return true
}
return false
}
/** 判断是否可以看到填报按钮 */
const canShowUploadButton = (row: ProjectInspectionRecordApi.ProjectInspectionRecordVO) => {
// 1. status != 2
if (row.status === 2) {
return false
}
// 2.
const inspectorIdNum = typeof row.inspector === 'string' ? Number(row.inspector) : row.inspector
const currentUserId = userStore.user.id
//
if (currentUserId === inspectorIdNum) {
return true
}
//
if (isAdmin()) {
return true
}
return false
}
/** 填报按钮操作 */
const handleUpload = async (row: ProjectInspectionRecordApi.ProjectInspectionRecordVO) => {
try {
// IDIDconfigType
const inspectorIdNum = typeof row.inspector === 'string' ? Number(row.inspector) : row.inspector
const configParams = {
projectId: row.projectId,
responsiblePersonId: inspectorIdNum,
status: 0, //
pageNo: 1,
pageSize: 99, //
}
const configData = await ProjectInspectionConfigApi.getProjectInspectionConfigPage(configParams)
if (!configData.list || configData.list.length === 0) {
message.warning('该巡检人没有配置巡检项目')
return
}
// configType
const configTypes = Array.from(
new Set(
configData.list
.map((config: ProjectInspectionConfigApi.ProjectInspectionConfigVO) => config.configType)
.filter((type: number | undefined) => type !== undefined)
)
) as number[]
if (configTypes.length === 0) {
message.warning('该巡检人没有配置巡检类型')
return
}
// configType
const inspectionDataList = await ProjectInspectionDataApi.getProjectInspectionDataList(configTypes)
if (!inspectionDataList || inspectionDataList.length === 0) {
message.warning('没有找到对应的巡检内容')
return
}
//
uploadFormRef.value.open(row, inspectionDataList)
} catch (error) {
console.error('获取巡检配置失败:', error)
message.error('获取巡检配置失败,请稍后重试')
}
}
/** 初始化 **/
onMounted(async () => {
//
projectList.value = await ProjectApi.getProjectSimpleList()
userList.value = await UserApi.getSimpleUserList()
//
await getList()
})
</script>