|
|
@@ -1,142 +1,142 @@
|
|
|
<template>
|
|
|
- <div class="p-2">
|
|
|
- <!-- 顶部统计卡片 -->
|
|
|
- <el-row :gutter="16" class="mb-[10px]">
|
|
|
- <el-col :span="6">
|
|
|
- <el-card shadow="hover">
|
|
|
- <div class="flex items-center justify-between">
|
|
|
+ <div class="hazard-page">
|
|
|
+ <div class="page-header">
|
|
|
+ <div class="page-title">安全隐患管理</div>
|
|
|
+ <div class="page-desc">管理各类型安全隐患信息,及时进行风险排查与处理</div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 顶部看板 -->
|
|
|
+ <el-row :gutter="16" class="stat-row">
|
|
|
+ <el-col :span="8">
|
|
|
+ <el-card shadow="hover" class="stat-card stat-card-total">
|
|
|
+ <div class="stat-inner">
|
|
|
<div>
|
|
|
- <p class="text-gray-500 text-sm">隐患总数</p>
|
|
|
- <p class="text-2xl font-bold text-blue-500">{{ statsData.totalCount }}</p>
|
|
|
+ <div class="stat-label">总隐患数量</div>
|
|
|
+ <div class="stat-value">{{ statsData.totalCount }}</div>
|
|
|
</div>
|
|
|
- <el-icon :size="40" color="#409EFF"><WarningFilled /></el-icon>
|
|
|
- </div>
|
|
|
- </el-card>
|
|
|
- </el-col>
|
|
|
- <el-col :span="6">
|
|
|
- <el-card shadow="hover">
|
|
|
- <div class="flex items-center justify-between">
|
|
|
- <div>
|
|
|
- <p class="text-gray-500 text-sm">待处理</p>
|
|
|
- <p class="text-2xl font-bold text-orange-500">{{ statsData.pendingCount }}</p>
|
|
|
+ <div class="stat-icon stat-icon-blue">
|
|
|
+ <el-icon><WarningFilled /></el-icon>
|
|
|
</div>
|
|
|
- <el-icon :size="40" color="#E6A23C"><Clock /></el-icon>
|
|
|
</div>
|
|
|
</el-card>
|
|
|
</el-col>
|
|
|
- <el-col :span="6">
|
|
|
- <el-card shadow="hover">
|
|
|
- <div class="flex items-center justify-between">
|
|
|
+
|
|
|
+ <el-col :span="8">
|
|
|
+ <el-card shadow="hover" class="stat-card stat-card-pending">
|
|
|
+ <div class="stat-inner">
|
|
|
<div>
|
|
|
- <p class="text-gray-500 text-sm">处理中</p>
|
|
|
- <p class="text-2xl font-bold text-yellow-500">{{ statsData.processingCount }}</p>
|
|
|
+ <div class="stat-label">未处理隐患</div>
|
|
|
+ <div class="stat-value stat-value-red">{{ statsData.unprocessedCount }}</div>
|
|
|
+ </div>
|
|
|
+ <div class="stat-icon stat-icon-red">
|
|
|
+ <el-icon><CircleCloseFilled /></el-icon>
|
|
|
</div>
|
|
|
- <el-icon :size="40" color="#F2C811"><Loading /></el-icon>
|
|
|
</div>
|
|
|
</el-card>
|
|
|
</el-col>
|
|
|
- <el-col :span="6">
|
|
|
- <el-card shadow="hover">
|
|
|
- <div class="flex items-center justify-between">
|
|
|
+
|
|
|
+ <el-col :span="8">
|
|
|
+ <el-card shadow="hover" class="stat-card stat-card-done">
|
|
|
+ <div class="stat-inner">
|
|
|
<div>
|
|
|
- <p class="text-gray-500 text-sm">已完成</p>
|
|
|
- <p class="text-2xl font-bold text-green-500">{{ statsData.completedCount }}</p>
|
|
|
+ <div class="stat-label">已处理隐患</div>
|
|
|
+ <div class="stat-value stat-value-green">{{ statsData.processedCount }}</div>
|
|
|
+ </div>
|
|
|
+ <div class="stat-icon stat-icon-green">
|
|
|
+ <el-icon><CircleCheckFilled /></el-icon>
|
|
|
</div>
|
|
|
- <el-icon :size="40" color="#67C23A"><CircleCheckFilled /></el-icon>
|
|
|
</div>
|
|
|
</el-card>
|
|
|
</el-col>
|
|
|
</el-row>
|
|
|
|
|
|
- <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
|
|
|
- <div v-show="showSearch" class="mb-[10px]">
|
|
|
- <el-card shadow="hover">
|
|
|
- <el-form ref="queryFormRef" :model="queryParams" :inline="true">
|
|
|
- <el-form-item label="编号" prop="hazardNo">
|
|
|
- <el-input v-model="queryParams.hazardNo" placeholder="请输入编号" clearable @keyup.enter="handleQuery" />
|
|
|
- </el-form-item>
|
|
|
- <el-form-item label="隐患名称" prop="hazardName">
|
|
|
- <el-input v-model="queryParams.hazardName" placeholder="请输入名称" clearable @keyup.enter="handleQuery" />
|
|
|
- </el-form-item>
|
|
|
+ <!-- 筛选区 -->
|
|
|
+ <el-card shadow="hover" class="search-card">
|
|
|
+ <el-form ref="queryFormRef" :model="queryParams" :inline="false" class="search-form">
|
|
|
+ <el-row :gutter="12">
|
|
|
+ <el-col :xs="24" :sm="12" :md="6">
|
|
|
<el-form-item label="隐患类型" prop="hazardType">
|
|
|
- <el-select v-model="queryParams.hazardType" placeholder="类型" clearable>
|
|
|
+ <el-select v-model="queryParams.hazardType" placeholder="全部类型" clearable class="w-full">
|
|
|
<el-option label="门店安全隐患" value="门店安全隐患" />
|
|
|
<el-option label="物流安全隐患" value="物流安全隐患" />
|
|
|
<el-option label="市场安全隐患" value="市场安全隐患" />
|
|
|
</el-select>
|
|
|
</el-form-item>
|
|
|
- <el-form-item label="风险等级" prop="riskLevel">
|
|
|
- <el-select v-model="queryParams.riskLevel" placeholder="等级" clearable>
|
|
|
- <el-option label="高风险" value="高风险" />
|
|
|
- <el-option label="中风险" value="中风险" />
|
|
|
- <el-option label="低风险" value="低风险" />
|
|
|
- </el-select>
|
|
|
- </el-form-item>
|
|
|
- <el-form-item label="状态" prop="processStatus">
|
|
|
- <el-select v-model="queryParams.processStatus" placeholder="状态" clearable>
|
|
|
+ </el-col>
|
|
|
+
|
|
|
+ <el-col :xs="24" :sm="12" :md="6">
|
|
|
+ <el-form-item label="处理状态" prop="processStatus">
|
|
|
+ <el-select v-model="queryParams.processStatus" placeholder="全部状态" clearable class="w-full">
|
|
|
<el-option label="待处理" value="待处理" />
|
|
|
<el-option label="处理中" value="处理中" />
|
|
|
<el-option label="已处理" value="已处理" />
|
|
|
</el-select>
|
|
|
</el-form-item>
|
|
|
- <el-form-item>
|
|
|
- <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
|
|
- <el-button icon="Refresh" @click="resetQuery">重置</el-button>
|
|
|
- </el-form-item>
|
|
|
- </el-form>
|
|
|
- </el-card>
|
|
|
- </div>
|
|
|
- </transition>
|
|
|
-
|
|
|
- <el-card shadow="hover">
|
|
|
- <template #header>
|
|
|
- <el-row :gutter="10">
|
|
|
- <el-col :span="1.5">
|
|
|
- <el-button v-has-permi="['riskctrl:hazard:add']" type="primary" plain icon="Plus" @click="handleAdd()">新增</el-button>
|
|
|
</el-col>
|
|
|
- <el-col :span="1.5">
|
|
|
- <el-button v-has-permi="['riskctrl:hazard:edit']" type="success" plain :disabled="single" icon="Edit" @click="handleUpdate()">修改</el-button>
|
|
|
- </el-col>
|
|
|
- <el-col :span="1.5">
|
|
|
- <el-button v-has-permi="['riskctrl:hazard:remove']" type="danger" plain :disabled="multiple" icon="Delete" @click="handleDelete()">删除</el-button>
|
|
|
+
|
|
|
+ <el-col :xs="24" :sm="12" :md="6">
|
|
|
+ <el-form-item label="风险等级" prop="riskLevel">
|
|
|
+ <el-select v-model="queryParams.riskLevel" placeholder="全部等级" clearable class="w-full">
|
|
|
+ <el-option label="高风险" value="高风险" />
|
|
|
+ <el-option label="中风险" value="中风险" />
|
|
|
+ <el-option label="低风险" value="低风险" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
</el-col>
|
|
|
- <el-col :span="1.5">
|
|
|
- <el-button v-has-permi="['riskctrl:hazard:process']" type="warning" plain :disabled="single" icon="Operation" @click="handleProcess()">处理</el-button>
|
|
|
+
|
|
|
+ <el-col :xs="24" :sm="12" :md="6">
|
|
|
+ <el-form-item label="关键词搜索" prop="keyword">
|
|
|
+ <el-input v-model="queryParams.keyword" placeholder="搜索隐患信息..." clearable :prefix-icon="Search" @keyup.enter="handleQuery" />
|
|
|
+ </el-form-item>
|
|
|
</el-col>
|
|
|
- <right-toolbar v-model:show-search="showSearch" :columns="columns" :search="true" @query-table="getList"></right-toolbar>
|
|
|
</el-row>
|
|
|
- </template>
|
|
|
|
|
|
+ <div class="search-actions">
|
|
|
+ <el-button @click="resetQuery">重置</el-button>
|
|
|
+ <el-button type="primary" @click="handleQuery">搜索</el-button>
|
|
|
+ <el-button type="success" @click="handleAdd">
|
|
|
+ <el-icon style="margin-right: 4px"><Plus /></el-icon>
|
|
|
+ 新增隐患
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+ </el-form>
|
|
|
+ </el-card>
|
|
|
+
|
|
|
+ <!-- 列表 -->
|
|
|
+ <el-card shadow="hover" class="table-card">
|
|
|
<el-table v-loading="loading" border :data="hazardList" @selection-change="handleSelectionChange">
|
|
|
<el-table-column type="selection" width="50" align="center" />
|
|
|
- <el-table-column v-if="columns[0].visible" key="hazardNo" label="编号" align="center" prop="hazardNo" width="140" />
|
|
|
- <el-table-column v-if="columns[1].visible" key="hazardName" label="隐患名称" align="center" prop="hazardName" :show-overflow-tooltip="true" />
|
|
|
- <el-table-column v-if="columns[2].visible" key="hazardType" label="类型" align="center" prop="hazardType" width="100" />
|
|
|
- <el-table-column v-if="columns[3].visible" key="riskLevel" label="风险等级" align="center" prop="riskLevel" width="100">
|
|
|
+ <el-table-column label="隐患编号" prop="hazardNo" width="160" align="center" />
|
|
|
+ <el-table-column label="隐患名称" prop="hazardName" min-width="180" align="center" :show-overflow-tooltip="true" />
|
|
|
+ <el-table-column label="隐患类型" prop="hazardType" width="140" align="center" />
|
|
|
+ <el-table-column label="风险等级" prop="riskLevel" width="120" align="center">
|
|
|
<template #default="scope">
|
|
|
- <el-tag :type="getLevelType(scope.row.riskLevel)">{{ scope.row.riskLevel }}</el-tag>
|
|
|
+ <span class="tag-pill" :class="getRiskClass(scope.row.riskLevel)">
|
|
|
+ {{ scope.row.riskLevel }}
|
|
|
+ </span>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
- <el-table-column v-if="columns[4].visible" key="hazardLocation" label="位置" align="center" prop="hazardLocation" :show-overflow-tooltip="true" />
|
|
|
- <el-table-column v-if="columns[5].visible" key="discoverTime" label="发现时间" align="center" prop="discoverTime" width="160" />
|
|
|
- <el-table-column v-if="columns[6].visible" key="processStatus" label="处理状态" align="center" prop="processStatus" width="100">
|
|
|
+ <el-table-column label="发现时间" prop="discoverTime" width="170" align="center" />
|
|
|
+ <el-table-column label="处理状态" prop="processStatus" width="120" align="center">
|
|
|
<template #default="scope">
|
|
|
- <el-tag :type="getStatusType(scope.row.processStatus)">{{ scope.row.processStatus }}</el-tag>
|
|
|
+ <span class="tag-pill" :class="getStatusClass(scope.row.processStatus)">
|
|
|
+ {{ scope.row.processStatus || '待处理' }}
|
|
|
+ </span>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
- <el-table-column v-if="columns[7].visible" key="processUser" label="处理人" align="center" prop="processUser" width="100" />
|
|
|
- <el-table-column v-if="columns[8].visible" label="创建时间" align="center" prop="createTime" width="160" />
|
|
|
- <el-table-column label="操作" fixed="right" width="220" class-name="small-padding fixed-width">
|
|
|
+ <el-table-column label="操作" fixed="right" width="210" align="center">
|
|
|
<template #default="scope">
|
|
|
- <el-tooltip content="修改" placement="top">
|
|
|
- <el-button v-has-permi="['riskctrl:hazard:edit']" link type="primary" icon="Edit" @click="handleUpdate(scope.row)"></el-button>
|
|
|
- </el-tooltip>
|
|
|
- <el-tooltip content="处理" placement="top">
|
|
|
- <el-button v-has-permi="['riskctrl:hazard:process']" link type="warning" icon="Operation" @click="handleProcess(scope.row)"></el-button>
|
|
|
- </el-tooltip>
|
|
|
- <el-tooltip content="删除" placement="top">
|
|
|
- <el-button v-has-permi="['riskctrl:hazard:remove']" link type="primary" icon="Delete" @click="handleDelete(scope.row)"></el-button>
|
|
|
- </el-tooltip>
|
|
|
+ <el-button link type="primary" @click="handleView(scope.row)">查看</el-button>
|
|
|
+ <el-button
|
|
|
+ v-if="scope.row.processStatus !== '已处理'"
|
|
|
+ v-has-permi="['riskctrl:hazard:process']"
|
|
|
+ link
|
|
|
+ type="success"
|
|
|
+ @click="handleProcess(scope.row)"
|
|
|
+ >
|
|
|
+ 处理
|
|
|
+ </el-button>
|
|
|
+ <el-button v-has-permi="['riskctrl:hazard:remove']" link type="danger" @click="handleDelete(scope.row)">删除</el-button>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
</el-table>
|
|
|
@@ -144,69 +144,136 @@
|
|
|
<pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" />
|
|
|
</el-card>
|
|
|
|
|
|
- <!-- 新增/修改弹窗 -->
|
|
|
- <el-dialog v-model="open" :title="title" width="750px" append-to-body>
|
|
|
- <el-form ref="hazardFormRef" :model="form" :rules="rules" label-width="100px">
|
|
|
- <el-row :gutter="20">
|
|
|
+ <!-- 新增/查看/修改弹窗 -->
|
|
|
+ <el-dialog v-model="open" :title="dialogTitle" width="760px" append-to-body destroy-on-close class="hazard-dialog">
|
|
|
+ <el-form ref="hazardFormRef" :model="form" :rules="rules" label-width="92px" class="hazard-form">
|
|
|
+ <el-row :gutter="18">
|
|
|
<el-col :span="12">
|
|
|
<el-form-item label="隐患名称" prop="hazardName">
|
|
|
- <el-input v-model="form.hazardName" placeholder="请输入隐患名称" />
|
|
|
+ <template v-if="isViewMode">
|
|
|
+ <div class="view-text">{{ form.hazardName || '-' }}</div>
|
|
|
+ </template>
|
|
|
+ <el-input v-else v-model="form.hazardName" placeholder="请输入隐患名称" />
|
|
|
</el-form-item>
|
|
|
</el-col>
|
|
|
+
|
|
|
<el-col :span="12">
|
|
|
- <el-form-item label="隐患类型" prop="hazardType">
|
|
|
- <el-select v-model="form.hazardType" placeholder="请选择类型" class="w-full">
|
|
|
- <el-option label="门店安全隐患" value="门店安全隐患" />
|
|
|
- <el-option label="物流安全隐患" value="物流安全隐患" />
|
|
|
- <el-option label="市场安全隐患" value="市场安全隐患" />
|
|
|
- </el-select>
|
|
|
+ <el-form-item label="隐患编号" prop="hazardNo">
|
|
|
+ <template v-if="isViewMode">
|
|
|
+ <div class="view-text">{{ form.hazardNo || '-' }}</div>
|
|
|
+ </template>
|
|
|
+ <el-input v-else v-model="form.hazardNo" placeholder="系统自动生成" disabled />
|
|
|
</el-form-item>
|
|
|
</el-col>
|
|
|
</el-row>
|
|
|
- <el-row :gutter="20">
|
|
|
+
|
|
|
+ <el-form-item label="隐患类型" prop="hazardType">
|
|
|
+ <template v-if="isViewMode">
|
|
|
+ <div class="view-text">{{ form.hazardType || '-' }}</div>
|
|
|
+ </template>
|
|
|
+ <el-select v-else v-model="form.hazardType" placeholder="请选择隐患类型" class="w-full">
|
|
|
+ <el-option label="门店安全隐患" value="门店安全隐患" />
|
|
|
+ <el-option label="物流安全隐患" value="物流安全隐患" />
|
|
|
+ <el-option label="市场安全隐患" value="市场安全隐患" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+
|
|
|
+ <el-row :gutter="18">
|
|
|
<el-col :span="12">
|
|
|
<el-form-item label="风险等级" prop="riskLevel">
|
|
|
- <el-select v-model="form.riskLevel" placeholder="请选择风险等级" class="w-full">
|
|
|
+ <template v-if="isViewMode">
|
|
|
+ <span class="tag-pill" :class="getRiskClass(form.riskLevel)">
|
|
|
+ {{ form.riskLevel || '-' }}
|
|
|
+ </span>
|
|
|
+ </template>
|
|
|
+ <el-select v-else v-model="form.riskLevel" placeholder="请选择风险等级" class="w-full">
|
|
|
<el-option label="高风险" value="高风险" />
|
|
|
<el-option label="中风险" value="中风险" />
|
|
|
<el-option label="低风险" value="低风险" />
|
|
|
</el-select>
|
|
|
</el-form-item>
|
|
|
</el-col>
|
|
|
+
|
|
|
<el-col :span="12">
|
|
|
<el-form-item label="发现时间" prop="discoverTime">
|
|
|
- <el-date-picker v-model="form.discoverTime" type="datetime" placeholder="选择时间" value-format="YYYY-MM-DD HH:mm:ss" class="w-full" />
|
|
|
+ <template v-if="isViewMode">
|
|
|
+ <div class="view-text">{{ form.discoverTime || '-' }}</div>
|
|
|
+ </template>
|
|
|
+ <el-date-picker
|
|
|
+ v-else
|
|
|
+ v-model="form.discoverTime"
|
|
|
+ type="datetime"
|
|
|
+ placeholder="年/月/日"
|
|
|
+ value-format="YYYY-MM-DD HH:mm:ss"
|
|
|
+ :disabled-date="disabledDiscoverDate"
|
|
|
+ class="w-full"
|
|
|
+ />
|
|
|
</el-form-item>
|
|
|
</el-col>
|
|
|
</el-row>
|
|
|
- <el-form-item label="位置" prop="hazardLocation">
|
|
|
- <el-input v-model="form.hazardLocation" placeholder="请输入隐患位置" />
|
|
|
+
|
|
|
+ <el-form-item label="隐患位置" prop="hazardLocation">
|
|
|
+ <template v-if="isViewMode">
|
|
|
+ <div class="view-text">{{ form.hazardLocation || '-' }}</div>
|
|
|
+ </template>
|
|
|
+ <el-input v-else v-model="form.hazardLocation" placeholder="请输入隐患具体位置" />
|
|
|
</el-form-item>
|
|
|
+
|
|
|
<el-form-item label="隐患描述" prop="hazardDesc">
|
|
|
- <el-input v-model="form.hazardDesc" type="textarea" :rows="3" placeholder="请输入隐患描述" />
|
|
|
- </el-form-item>
|
|
|
- <el-form-item label="状态" prop="status">
|
|
|
- <el-radio-group v-model="form.status">
|
|
|
- <el-radio v-for="dict in sys_normal_disable" :key="dict.value" :label="dict.value">{{ dict.label }}</el-radio>
|
|
|
- </el-radio-group>
|
|
|
+ <template v-if="isViewMode">
|
|
|
+ <div class="view-text view-text-multiline">{{ form.hazardDesc || '-' }}</div>
|
|
|
+ </template>
|
|
|
+ <el-input v-else v-model="form.hazardDesc" type="textarea" :rows="4" placeholder="请详细描述隐患情况..." />
|
|
|
</el-form-item>
|
|
|
- <el-form-item label="备注" prop="remark">
|
|
|
- <el-input v-model="form.remark" type="textarea" placeholder="请输入备注" />
|
|
|
+
|
|
|
+ <el-form-item label="上传照片" prop="photoList">
|
|
|
+ <template v-if="isViewMode">
|
|
|
+ <div v-if="photoViewList.length" class="photo-view-list">
|
|
|
+ <div v-for="(item, index) in photoViewList" :key="index" class="photo-view-item">
|
|
|
+ <el-image :src="item" fit="cover" class="photo-thumb" :preview-src-list="photoViewList" preview-teleported />
|
|
|
+<!-- <div class="photo-path">{{ item }}</div>-->
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div v-else class="view-text">-</div>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <el-upload
|
|
|
+ v-else
|
|
|
+ v-model:file-list="photoFileList"
|
|
|
+ :action="uploadUrl"
|
|
|
+ :headers="uploadHeaders"
|
|
|
+ list-type="picture-card"
|
|
|
+ :auto-upload="true"
|
|
|
+ accept=".jpg,.jpeg,.png"
|
|
|
+ :limit="5"
|
|
|
+ :on-success="handlePhotoSuccess"
|
|
|
+ :on-remove="handlePhotoRemove"
|
|
|
+ :on-exceed="handlePhotoExceed"
|
|
|
+ :before-upload="beforePhotoUpload"
|
|
|
+ class="upload-wrap"
|
|
|
+ >
|
|
|
+ <el-icon><Plus /></el-icon>
|
|
|
+ <template #tip>
|
|
|
+ <div class="el-upload__tip">仅支持 jpg/png/jpeg,最多 5 张</div>
|
|
|
+ </template>
|
|
|
+ </el-upload>
|
|
|
</el-form-item>
|
|
|
</el-form>
|
|
|
+
|
|
|
<template #footer>
|
|
|
<div class="dialog-footer">
|
|
|
- <el-button type="primary" @click="submitForm">确 定</el-button>
|
|
|
- <el-button @click="cancel">取 消</el-button>
|
|
|
+ <el-button v-if="!isViewMode" @click="cancel">取消</el-button>
|
|
|
+ <el-button v-if="isViewMode" type="primary" @click="open = false">关闭</el-button>
|
|
|
+ <el-button v-else type="primary" @click="submitForm">提交保存</el-button>
|
|
|
</div>
|
|
|
</template>
|
|
|
</el-dialog>
|
|
|
|
|
|
<!-- 处理弹窗 -->
|
|
|
- <el-dialog v-model="processOpen" title="处理隐患" width="600px" append-to-body>
|
|
|
+ <el-dialog v-model="processOpen" title="处理隐患" width="620px" append-to-body>
|
|
|
<el-form ref="processFormRef" :model="processForm" :rules="processRules" label-width="100px">
|
|
|
<el-form-item label="处理状态" prop="processStatus">
|
|
|
- <el-select v-model="processForm.processStatus" placeholder="请选择" class="w-full">
|
|
|
+ <el-select v-model="processForm.processStatus" placeholder="请选择处理状态" class="w-full">
|
|
|
<el-option label="处理中" value="处理中" />
|
|
|
<el-option label="已处理" value="已处理" />
|
|
|
</el-select>
|
|
|
@@ -220,8 +287,8 @@
|
|
|
</el-form>
|
|
|
<template #footer>
|
|
|
<div class="dialog-footer">
|
|
|
- <el-button type="primary" @click="submitProcess">确 定</el-button>
|
|
|
<el-button @click="processOpen = false">取 消</el-button>
|
|
|
+ <el-button type="primary" @click="submitProcess">确 定</el-button>
|
|
|
</div>
|
|
|
</template>
|
|
|
</el-dialog>
|
|
|
@@ -230,7 +297,9 @@
|
|
|
|
|
|
<script setup name="SafetyHazard" lang="ts">
|
|
|
import { listHazard, getHazard, delHazard, addHazard, updateHazard, processHazard, getHazardStats } from '@/api/riskctrl/hazard';
|
|
|
-import { HazardForm, HazardQuery, HazardVO, HazardStats } from '@/api/riskctrl/hazard/types';
|
|
|
+import { HazardForm, HazardQuery, HazardVO, HazardStats, HazardStatsVo } from '@/api/riskctrl/hazard/types';
|
|
|
+import { Plus, Search } from '@element-plus/icons-vue';
|
|
|
+import { getToken } from '@/utils/auth';
|
|
|
|
|
|
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
|
|
const { sys_normal_disable } = proxy.useDict('sys_normal_disable');
|
|
|
@@ -242,19 +311,97 @@ const ids = ref<Array<string | number>>([]);
|
|
|
const single = ref(true);
|
|
|
const multiple = ref(true);
|
|
|
const total = ref(0);
|
|
|
-const title = ref('');
|
|
|
const open = ref(false);
|
|
|
const processOpen = ref(false);
|
|
|
|
|
|
+const dialogMode = ref<'add' | 'edit' | 'view'>('add');
|
|
|
+const dialogTitle = computed(() => {
|
|
|
+ if (dialogMode.value === 'add') return '新增安全隐患';
|
|
|
+ if (dialogMode.value === 'edit') return '修改安全隐患';
|
|
|
+ return '查看安全隐患';
|
|
|
+});
|
|
|
+const isViewMode = computed(() => dialogMode.value === 'view');
|
|
|
+
|
|
|
const queryFormRef = ref<ElFormInstance>();
|
|
|
const hazardFormRef = ref<ElFormInstance>();
|
|
|
const processFormRef = ref<ElFormInstance>();
|
|
|
|
|
|
-const statsData = reactive<HazardStats>({
|
|
|
+const uploadUrl = ref(import.meta.env.VITE_APP_BASE_API + '/resource/oss/upload');
|
|
|
+const uploadHeaders = ref({
|
|
|
+ Authorization: 'Bearer ' + getToken(),
|
|
|
+ clientid: import.meta.env.VITE_APP_CLIENT_ID
|
|
|
+});
|
|
|
+
|
|
|
+const photoFileList = ref<any[]>([]);
|
|
|
+
|
|
|
+const photoViewList = computed(() => {
|
|
|
+ const urls = form.photoUrls;
|
|
|
+ if (!urls) return [];
|
|
|
+ return String(urls)
|
|
|
+ .split(',')
|
|
|
+ .map((item) => item.trim())
|
|
|
+ .filter(Boolean);
|
|
|
+});
|
|
|
+
|
|
|
+const syncPhotoUrls = () => {
|
|
|
+ form.photoUrls = photoFileList.value
|
|
|
+ .map((f: any) => f.response?.data?.url || f.url)
|
|
|
+ .filter(Boolean)
|
|
|
+ .join(',');
|
|
|
+};
|
|
|
+
|
|
|
+const initPhotoFileList = (urls?: string) => {
|
|
|
+ if (!urls) {
|
|
|
+ photoFileList.value = [];
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ photoFileList.value = String(urls)
|
|
|
+ .split(',')
|
|
|
+ .map((url, idx) => ({
|
|
|
+ name: `photo_${idx + 1}`,
|
|
|
+ url,
|
|
|
+ response: { data: { url } }
|
|
|
+ }))
|
|
|
+ .filter((item) => item.url);
|
|
|
+};
|
|
|
+
|
|
|
+const handlePhotoSuccess = (response: any) => {
|
|
|
+ if (response?.code !== 200) {
|
|
|
+ proxy?.$modal.msgError(response?.msg || '上传失败');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ syncPhotoUrls();
|
|
|
+};
|
|
|
+
|
|
|
+const handlePhotoRemove = () => {
|
|
|
+ syncPhotoUrls();
|
|
|
+};
|
|
|
+
|
|
|
+const handlePhotoExceed = () => {
|
|
|
+ proxy?.$modal.msgWarning('最多只能上传 5 张图片');
|
|
|
+};
|
|
|
+
|
|
|
+const beforePhotoUpload = (file: File) => {
|
|
|
+ const isImage = ['image/jpeg', 'image/png', 'image/jpg'].includes(file.type);
|
|
|
+ if (!isImage) {
|
|
|
+ proxy?.$modal.msgError('只能上传 jpg/png/jpeg 图片');
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ const isLt5M = file.size / 1024 / 1024 < 5;
|
|
|
+ if (!isLt5M) {
|
|
|
+ proxy?.$modal.msgError('单张图片不能超过 5MB');
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ return true;
|
|
|
+};
|
|
|
+
|
|
|
+const unwrap = <T,>(res: any): T => (res?.data ?? res) as T;
|
|
|
+
|
|
|
+const statsData = reactive<HazardStatsVo>({
|
|
|
totalCount: 0,
|
|
|
- pendingCount: 0,
|
|
|
- processingCount: 0,
|
|
|
- completedCount: 0
|
|
|
+ unprocessedCount: 0,
|
|
|
+ processedCount: 0
|
|
|
});
|
|
|
|
|
|
const queryParams = reactive<HazardQuery>({
|
|
|
@@ -264,17 +411,21 @@ const queryParams = reactive<HazardQuery>({
|
|
|
hazardName: undefined,
|
|
|
hazardType: undefined,
|
|
|
riskLevel: undefined,
|
|
|
- processStatus: undefined
|
|
|
+ processStatus: undefined,
|
|
|
+ keyword: undefined
|
|
|
});
|
|
|
|
|
|
-const form = reactive<HazardForm>({
|
|
|
+const form = reactive<any>({
|
|
|
hazardId: undefined,
|
|
|
+ hazardNo: '',
|
|
|
hazardName: '',
|
|
|
hazardType: '',
|
|
|
- riskLevel: '中风险',
|
|
|
+ riskLevel: '',
|
|
|
discoverTime: '',
|
|
|
hazardLocation: '',
|
|
|
hazardDesc: '',
|
|
|
+ photoUrls: '',
|
|
|
+ processStatus: '待处理',
|
|
|
status: '0',
|
|
|
remark: ''
|
|
|
});
|
|
|
@@ -289,8 +440,10 @@ const processForm = reactive({
|
|
|
|
|
|
const rules = {
|
|
|
hazardName: [{ required: true, message: '隐患名称不能为空', trigger: 'blur' }],
|
|
|
- hazardType: [{ required: true, message: '类型不能为空', trigger: 'change' }],
|
|
|
- riskLevel: [{ required: true, message: '风险等级不能为空', trigger: 'change' }]
|
|
|
+ hazardType: [{ required: true, message: '隐患类型不能为空', trigger: 'change' }],
|
|
|
+ riskLevel: [{ required: true, message: '风险等级不能为空', trigger: 'change' }],
|
|
|
+ discoverTime: [{ required: true, message: '发现时间不能为空', trigger: 'change' }],
|
|
|
+ hazardLocation: [{ required: true, message: '隐患位置不能为空', trigger: 'blur' }]
|
|
|
};
|
|
|
|
|
|
const processRules = {
|
|
|
@@ -298,155 +451,492 @@ const processRules = {
|
|
|
processResult: [{ required: true, message: '处理结果不能为空', trigger: 'blur' }]
|
|
|
};
|
|
|
|
|
|
-const columns = ref([
|
|
|
- { key: 0, label: '编号', visible: true },
|
|
|
- { key: 1, label: '隐患名称', visible: true },
|
|
|
- { key: 2, label: '类型', visible: true },
|
|
|
- { key: 3, label: '风险等级', visible: true },
|
|
|
- { key: 4, label: '位置', visible: true },
|
|
|
- { key: 5, label: '发现时间', visible: true },
|
|
|
- { key: 6, label: '处理状态', visible: true },
|
|
|
- { key: 7, label: '处理人', visible: true },
|
|
|
- { key: 8, label: '创建时间', visible: true }
|
|
|
-]);
|
|
|
-
|
|
|
-type TagType = 'success' | 'warning' | 'danger' | 'info' | 'primary';
|
|
|
-
|
|
|
-const getLevelType = (level: string): TagType => {
|
|
|
- const map: Record<string, TagType> = {
|
|
|
- '高风险': 'danger',
|
|
|
- '中风险': 'warning',
|
|
|
- '低风险': 'info'
|
|
|
+const getLevelType = (level: string) => {
|
|
|
+ const map: Record<string, 'danger' | 'warning' | 'info' | 'success'> = {
|
|
|
+ 高风险: 'danger',
|
|
|
+ 中风险: 'warning',
|
|
|
+ 低风险: 'success'
|
|
|
};
|
|
|
return map[level] || 'info';
|
|
|
};
|
|
|
|
|
|
-const getStatusType = (status: string): TagType => {
|
|
|
- const map: Record<string, TagType> = {
|
|
|
- '待处理': 'danger',
|
|
|
- '处理中': 'warning',
|
|
|
- '已处理': 'success'
|
|
|
+const getStatusType = (status: string) => {
|
|
|
+ const map: Record<string, 'danger' | 'warning' | 'success' | 'info'> = {
|
|
|
+ 待处理: 'danger',
|
|
|
+ 处理中: 'warning',
|
|
|
+ 已处理: 'success'
|
|
|
};
|
|
|
return map[status] || 'info';
|
|
|
};
|
|
|
|
|
|
+const getRiskClass = (level: string) => {
|
|
|
+ const map: Record<string, string> = {
|
|
|
+ 高风险: 'risk-high',
|
|
|
+ 中风险: 'risk-medium',
|
|
|
+ 低风险: 'risk-low'
|
|
|
+ };
|
|
|
+ return map[level] || 'risk-low';
|
|
|
+};
|
|
|
+
|
|
|
+const getStatusClass = (status: string) => {
|
|
|
+ const map: Record<string, string> = {
|
|
|
+ 待处理: 'status-wait',
|
|
|
+ 处理中: 'status-processing',
|
|
|
+ 已处理: 'status-done'
|
|
|
+ };
|
|
|
+ return map[status] || 'status-wait';
|
|
|
+};
|
|
|
+
|
|
|
+const disabledDiscoverDate = (time: Date) => {
|
|
|
+ return time.getTime() > Date.now();
|
|
|
+};
|
|
|
+
|
|
|
+const handleExceed = () => {
|
|
|
+ proxy?.$modal.msgWarning('最多只能上传 5 张图片');
|
|
|
+};
|
|
|
+
|
|
|
const loadStats = async () => {
|
|
|
try {
|
|
|
- const res = await getHazardStats();
|
|
|
- Object.assign(statsData, res.data);
|
|
|
- } catch { /* ignore */ }
|
|
|
+ const res = await getHazardStats(queryParams as HazardQuery);
|
|
|
+ const data = unwrap<any>(res);
|
|
|
+
|
|
|
+ statsData.totalCount = data.totalCount ?? data.total ?? 0;
|
|
|
+ statsData.unprocessedCount = data.unprocessedCount ?? data.unprocessed ?? 0;
|
|
|
+ statsData.processedCount = data.processedCount ?? data.processed ?? 0;
|
|
|
+ } catch (error) {
|
|
|
+ console.error('获取隐患统计失败:', error);
|
|
|
+ statsData.totalCount = 0;
|
|
|
+ statsData.unprocessedCount = 0;
|
|
|
+ statsData.processedCount = 0;
|
|
|
+ }
|
|
|
};
|
|
|
|
|
|
const getList = async () => {
|
|
|
loading.value = true;
|
|
|
- const res = await listHazard(queryParams);
|
|
|
- hazardList.value = res.rows;
|
|
|
- total.value = res.total;
|
|
|
- loading.value = false;
|
|
|
+ try {
|
|
|
+ const res = await listHazard(queryParams);
|
|
|
+ const data = unwrap<{ rows?: HazardVO[]; total?: number }>(res);
|
|
|
+
|
|
|
+ hazardList.value = data.rows || [];
|
|
|
+ total.value = data.total || 0;
|
|
|
+ } catch (error) {
|
|
|
+ console.error('获取隐患列表失败:', error);
|
|
|
+ hazardList.value = [];
|
|
|
+ total.value = 0;
|
|
|
+ } finally {
|
|
|
+ loading.value = false;
|
|
|
+ }
|
|
|
};
|
|
|
|
|
|
-const cancel = () => {
|
|
|
- open.value = false;
|
|
|
- reset();
|
|
|
+const handleQuery = async () => {
|
|
|
+ queryParams.pageNum = 1;
|
|
|
+ await loadStats();
|
|
|
+ await getList();
|
|
|
+};
|
|
|
+
|
|
|
+const resetQuery = async () => {
|
|
|
+ queryFormRef.value?.resetFields();
|
|
|
+ queryParams.hazardNo = undefined;
|
|
|
+ queryParams.hazardName = undefined;
|
|
|
+ queryParams.hazardType = undefined;
|
|
|
+ queryParams.riskLevel = undefined;
|
|
|
+ queryParams.processStatus = undefined;
|
|
|
+ queryParams.keyword = undefined;
|
|
|
+ queryParams.pageNum = 1;
|
|
|
+ await loadStats();
|
|
|
+ await getList();
|
|
|
+};
|
|
|
+
|
|
|
+const handleSelectionChange = (selection: HazardVO[]) => {
|
|
|
+ ids.value = selection.map((item) => item.hazardId);
|
|
|
+ single.value = selection.length !== 1;
|
|
|
+ multiple.value = !selection.length;
|
|
|
};
|
|
|
|
|
|
-const reset = () => {
|
|
|
+const resetForm = () => {
|
|
|
form.hazardId = undefined;
|
|
|
+ form.hazardNo = '';
|
|
|
form.hazardName = '';
|
|
|
form.hazardType = '';
|
|
|
- form.riskLevel = '中风险';
|
|
|
+ form.riskLevel = '';
|
|
|
form.discoverTime = '';
|
|
|
form.hazardLocation = '';
|
|
|
form.hazardDesc = '';
|
|
|
+ form.processStatus = '待处理';
|
|
|
form.status = '0';
|
|
|
form.remark = '';
|
|
|
+ photoFileList.value = [];
|
|
|
nextTick(() => {
|
|
|
- hazardFormRef.value?.resetFields();
|
|
|
+ hazardFormRef.value?.clearValidate();
|
|
|
});
|
|
|
};
|
|
|
|
|
|
-const handleQuery = () => {
|
|
|
- queryParams.pageNum = 1;
|
|
|
- getList();
|
|
|
+const handleAdd = () => {
|
|
|
+ dialogMode.value = 'add';
|
|
|
+ resetForm();
|
|
|
+ form.processStatus = '待处理';
|
|
|
+ open.value = true;
|
|
|
};
|
|
|
|
|
|
-const resetQuery = () => {
|
|
|
- queryFormRef.value?.resetFields();
|
|
|
- handleQuery();
|
|
|
-};
|
|
|
+const handleView = async (row: HazardVO) => {
|
|
|
+ dialogMode.value = 'view';
|
|
|
+ resetForm();
|
|
|
|
|
|
-const handleSelectionChange = (selection: HazardVO[]) => {
|
|
|
- ids.value = selection.map((item) => item.hazardId);
|
|
|
- single.value = selection.length !== 1;
|
|
|
- multiple.value = !selection.length;
|
|
|
-};
|
|
|
+ const res = await getHazard(row.hazardId);
|
|
|
+ const data = unwrap<HazardVO>(res);
|
|
|
|
|
|
-const handleAdd = () => {
|
|
|
- reset();
|
|
|
+ Object.assign(form, data);
|
|
|
+ initPhotoFileList(data.photoUrls);
|
|
|
open.value = true;
|
|
|
- title.value = '新增安全隐患';
|
|
|
};
|
|
|
|
|
|
const handleUpdate = async (row?: HazardVO) => {
|
|
|
- reset();
|
|
|
+ dialogMode.value = 'edit';
|
|
|
+ resetForm();
|
|
|
+
|
|
|
const hazardId = row?.hazardId || ids.value[0];
|
|
|
const res = await getHazard(hazardId);
|
|
|
- Object.assign(form, res.data);
|
|
|
+ const data = unwrap<HazardVO>(res);
|
|
|
+
|
|
|
+ Object.assign(form, data);
|
|
|
+ initPhotoFileList(data.photoUrls);
|
|
|
open.value = true;
|
|
|
- title.value = '修改安全隐患';
|
|
|
+};
|
|
|
+
|
|
|
+const cancel = () => {
|
|
|
+ open.value = false;
|
|
|
+ resetForm();
|
|
|
};
|
|
|
|
|
|
const submitForm = () => {
|
|
|
hazardFormRef.value?.validate(async (valid: boolean) => {
|
|
|
- if (valid) {
|
|
|
- if (form.hazardId !== undefined) {
|
|
|
- await updateHazard(form);
|
|
|
- proxy?.$modal.msgSuccess('修改成功');
|
|
|
- } else {
|
|
|
- await addHazard(form);
|
|
|
- proxy?.$modal.msgSuccess('新增成功');
|
|
|
- }
|
|
|
- open.value = false;
|
|
|
- await getList();
|
|
|
- await loadStats();
|
|
|
+ if (!valid) return;
|
|
|
+
|
|
|
+ syncPhotoUrls();
|
|
|
+
|
|
|
+ if (form.hazardId !== undefined) {
|
|
|
+ await updateHazard(form);
|
|
|
+ proxy?.$modal.msgSuccess('修改成功');
|
|
|
+ } else {
|
|
|
+ form.processStatus = '待处理';
|
|
|
+ await addHazard(form);
|
|
|
+ proxy?.$modal.msgSuccess('新增成功');
|
|
|
}
|
|
|
+
|
|
|
+ open.value = false;
|
|
|
+ await loadStats();
|
|
|
+ await getList();
|
|
|
});
|
|
|
};
|
|
|
|
|
|
const handleProcess = async (row?: HazardVO) => {
|
|
|
const hazardId = row?.hazardId || ids.value[0];
|
|
|
const res = await getHazard(hazardId);
|
|
|
- processForm.hazardId = res.data.hazardId;
|
|
|
- processForm.processStatus = res.data.processStatus || '处理中';
|
|
|
- processForm.processResult = res.data.processResult || '';
|
|
|
- processForm.processUser = res.data.processUser || '';
|
|
|
+ const data = unwrap<HazardVO>(res);
|
|
|
+
|
|
|
+ processForm.hazardId = data.hazardId;
|
|
|
+ processForm.processStatus = data.processStatus || '处理中';
|
|
|
+ processForm.processResult = data.processResult || '';
|
|
|
+ processForm.processUser = data.processUser || '';
|
|
|
processOpen.value = true;
|
|
|
};
|
|
|
|
|
|
const submitProcess = () => {
|
|
|
processFormRef.value?.validate(async (valid: boolean) => {
|
|
|
- if (valid) {
|
|
|
- processForm.processTime = new Date().toISOString();
|
|
|
- await processHazard(processForm as any);
|
|
|
- proxy?.$modal.msgSuccess('处理成功');
|
|
|
- processOpen.value = false;
|
|
|
- await getList();
|
|
|
- await loadStats();
|
|
|
- }
|
|
|
+ if (!valid) return;
|
|
|
+
|
|
|
+ processForm.processTime = new Date().toISOString();
|
|
|
+ await processHazard(processForm as any);
|
|
|
+ proxy?.$modal.msgSuccess('处理成功');
|
|
|
+ processOpen.value = false;
|
|
|
+ await loadStats();
|
|
|
+ await getList();
|
|
|
});
|
|
|
};
|
|
|
|
|
|
const handleDelete = async (row?: HazardVO) => {
|
|
|
const hazardIds = row?.hazardId || ids.value;
|
|
|
- await proxy?.$modal.confirm('是否确认删除编号为"' + hazardIds + '"的数据项?');
|
|
|
+ await proxy?.$modal.confirm(`是否确认删除该隐患数据?`);
|
|
|
await delHazard(hazardIds);
|
|
|
- await getList();
|
|
|
- await loadStats();
|
|
|
proxy?.$modal.msgSuccess('删除成功');
|
|
|
+ await loadStats();
|
|
|
+ await getList();
|
|
|
};
|
|
|
|
|
|
-onMounted(() => {
|
|
|
- loadStats();
|
|
|
- getList();
|
|
|
+onMounted(async () => {
|
|
|
+ await loadStats();
|
|
|
+ await getList();
|
|
|
});
|
|
|
</script>
|
|
|
+
|
|
|
+<style lang="scss" scoped>
|
|
|
+.hazard-page {
|
|
|
+ padding: 16px;
|
|
|
+ background: #f5f7fb;
|
|
|
+ min-height: calc(100vh - 50px);
|
|
|
+}
|
|
|
+
|
|
|
+.hazard-dialog {
|
|
|
+ :deep(.el-dialog__header) {
|
|
|
+ margin-right: 0;
|
|
|
+ padding: 16px 20px 12px;
|
|
|
+ border-bottom: 1px solid #e5e7eb;
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(.el-dialog__body) {
|
|
|
+ padding: 18px 20px 10px;
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(.el-dialog__footer) {
|
|
|
+ padding: 12px 20px 18px;
|
|
|
+ border-top: 1px solid #f1f5f9;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.hazard-form {
|
|
|
+ :deep(.el-form-item) {
|
|
|
+ margin-bottom: 16px;
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(.el-form-item__label) {
|
|
|
+ font-weight: 600;
|
|
|
+ color: #374151;
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(.el-input__wrapper),
|
|
|
+ :deep(.el-textarea__inner),
|
|
|
+ :deep(.el-select__wrapper) {
|
|
|
+ border-radius: 6px;
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(.el-input.is-disabled .el-input__wrapper) {
|
|
|
+ background: #bfbfbf;
|
|
|
+ box-shadow: none;
|
|
|
+ color: #fff;
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(.el-input.is-disabled .el-input__inner) {
|
|
|
+ color: #fff;
|
|
|
+ -webkit-text-fill-color: #fff;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.upload-wrap {
|
|
|
+ width: 100%;
|
|
|
+}
|
|
|
+
|
|
|
+.dialog-footer {
|
|
|
+ display: flex;
|
|
|
+ justify-content: flex-end;
|
|
|
+ gap: 10px;
|
|
|
+}
|
|
|
+
|
|
|
+.page-header {
|
|
|
+ margin-bottom: 16px;
|
|
|
+}
|
|
|
+
|
|
|
+.view-text {
|
|
|
+ width: 100%;
|
|
|
+ min-height: 32px;
|
|
|
+ line-height: 32px;
|
|
|
+ padding: 0 12px;
|
|
|
+ border-radius: 6px;
|
|
|
+ background: #f8fafc;
|
|
|
+ color: #334155;
|
|
|
+ border: 1px solid #e5e7eb;
|
|
|
+ word-break: break-all;
|
|
|
+}
|
|
|
+
|
|
|
+.view-text-multiline {
|
|
|
+ min-height: 88px;
|
|
|
+ padding-top: 6px;
|
|
|
+ padding-bottom: 6px;
|
|
|
+ line-height: 1.6;
|
|
|
+}
|
|
|
+
|
|
|
+.photo-view-list {
|
|
|
+ display: flex;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ gap: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+.photo-view-item {
|
|
|
+ width: 140px;
|
|
|
+}
|
|
|
+
|
|
|
+.photo-thumb {
|
|
|
+ width: 140px;
|
|
|
+ height: 100px;
|
|
|
+ border-radius: 8px;
|
|
|
+ margin-bottom: 6px;
|
|
|
+ border: 1px solid #e5e7eb;
|
|
|
+}
|
|
|
+
|
|
|
+.photo-path {
|
|
|
+ font-size: 12px;
|
|
|
+ color: #64748b;
|
|
|
+ word-break: break-all;
|
|
|
+}
|
|
|
+
|
|
|
+.page-title {
|
|
|
+ font-size: 22px;
|
|
|
+ font-weight: 700;
|
|
|
+ color: #1f2937;
|
|
|
+ margin-bottom: 6px;
|
|
|
+}
|
|
|
+
|
|
|
+.page-desc {
|
|
|
+ font-size: 14px;
|
|
|
+ color: #6b7280;
|
|
|
+}
|
|
|
+
|
|
|
+.stat-row {
|
|
|
+ margin-bottom: 16px;
|
|
|
+}
|
|
|
+
|
|
|
+.stat-card {
|
|
|
+ border-radius: 14px;
|
|
|
+ border: none;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+
|
|
|
+.stat-inner {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ min-height: 88px;
|
|
|
+}
|
|
|
+
|
|
|
+.stat-label {
|
|
|
+ font-size: 14px;
|
|
|
+ color: #6b7280;
|
|
|
+ margin-bottom: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+.stat-value {
|
|
|
+ font-size: 34px;
|
|
|
+ font-weight: 700;
|
|
|
+ color: #111827;
|
|
|
+ line-height: 1;
|
|
|
+}
|
|
|
+
|
|
|
+.stat-value-red {
|
|
|
+ color: #ef4444;
|
|
|
+}
|
|
|
+
|
|
|
+.stat-value-green {
|
|
|
+ color: #22c55e;
|
|
|
+}
|
|
|
+
|
|
|
+.stat-icon {
|
|
|
+ width: 44px;
|
|
|
+ height: 44px;
|
|
|
+ border-radius: 50%;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ font-size: 22px;
|
|
|
+}
|
|
|
+
|
|
|
+.stat-icon-blue {
|
|
|
+ background: #e7efff;
|
|
|
+ color: #2563eb;
|
|
|
+}
|
|
|
+
|
|
|
+.stat-icon-red {
|
|
|
+ background: #ffe7e7;
|
|
|
+ color: #ef4444;
|
|
|
+}
|
|
|
+
|
|
|
+.stat-icon-green {
|
|
|
+ background: #e8f8ef;
|
|
|
+ color: #22c55e;
|
|
|
+}
|
|
|
+
|
|
|
+.tag-pill {
|
|
|
+ display: inline-flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ min-width: 72px;
|
|
|
+ height: 28px;
|
|
|
+ padding: 0 12px;
|
|
|
+ border-radius: 999px;
|
|
|
+ font-size: 12px;
|
|
|
+ font-weight: 600;
|
|
|
+ line-height: 1;
|
|
|
+}
|
|
|
+
|
|
|
+.risk-high {
|
|
|
+ background: #fee2e2;
|
|
|
+ color: #dc2626;
|
|
|
+ border: 1px solid #fecaca;
|
|
|
+}
|
|
|
+
|
|
|
+.risk-medium {
|
|
|
+ background: #ffedd5;
|
|
|
+ color: #ea580c;
|
|
|
+ border: 1px solid #fed7aa;
|
|
|
+}
|
|
|
+
|
|
|
+.risk-low {
|
|
|
+ background: #fef3c7;
|
|
|
+ color: #ca8a04;
|
|
|
+ border: 1px solid #fde68a;
|
|
|
+}
|
|
|
+
|
|
|
+.status-wait {
|
|
|
+ background: #f1f5f9;
|
|
|
+ color: #64748b;
|
|
|
+ border: 1px solid #e2e8f0;
|
|
|
+}
|
|
|
+
|
|
|
+.status-processing {
|
|
|
+ background: #fef3c7;
|
|
|
+ color: #ca8a04;
|
|
|
+ border: 1px solid #fde68a;
|
|
|
+}
|
|
|
+
|
|
|
+.status-done {
|
|
|
+ background: #dcfce7;
|
|
|
+ color: #16a34a;
|
|
|
+ border: 1px solid #bbf7d0;
|
|
|
+}
|
|
|
+
|
|
|
+.search-card,
|
|
|
+.table-card {
|
|
|
+ border-radius: 14px;
|
|
|
+ border: none;
|
|
|
+ margin-bottom: 16px;
|
|
|
+}
|
|
|
+
|
|
|
+.search-form {
|
|
|
+ width: 100%;
|
|
|
+}
|
|
|
+
|
|
|
+.search-actions {
|
|
|
+ display: flex;
|
|
|
+ justify-content: flex-end;
|
|
|
+ gap: 10px;
|
|
|
+ margin-top: 6px;
|
|
|
+}
|
|
|
+
|
|
|
+.upload-tip {
|
|
|
+ margin-left: 12px;
|
|
|
+ color: #94a3b8;
|
|
|
+ font-size: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+.dialog-footer {
|
|
|
+ display: flex;
|
|
|
+ justify-content: flex-end;
|
|
|
+ gap: 10px;
|
|
|
+}
|
|
|
+
|
|
|
+:deep(.el-card__body) {
|
|
|
+ padding: 18px 18px 16px;
|
|
|
+}
|
|
|
+
|
|
|
+:deep(.el-form-item) {
|
|
|
+ margin-bottom: 14px;
|
|
|
+}
|
|
|
+</style>
|