Browse Source

部分UI和功能调整,包含积分新增权限,和列展示

master
DU 1 month ago
parent
commit
ca1729b639
  1. 4
      src/api/points/rule.js
  2. 398
      src/views/hazard/ledger/index.vue
  3. 2
      src/views/login.vue
  4. 101
      src/views/points/management/index.vue

4
src/api/points/rule.js

@ -8,6 +8,10 @@ export function listFactoryDepartments() {
return request({ url: '/system/dept/factoryList', method: 'get' }) return request({ url: '/system/dept/factoryList', method: 'get' })
} }
export function getMyFactoryDepartment() {
return request({ url: '/system/dept/myFactory', method: 'get' })
}
export function togglePointsRule(id, enabled) { export function togglePointsRule(id, enabled) {
return request({ return request({
url: '/business/points/rule/toggleEnabled', url: '/business/points/rule/toggleEnabled',

398
src/views/hazard/ledger/index.vue

@ -1,41 +1,197 @@
<template> <template>
<div class="app-container hazard-ledger"> <div class="app-container hazard-ledger">
<el-form ref="queryForm" :model="queryParams" size="small" :inline="true" class="filter-bar"> <data-list-panel modern-table>
<el-form-item label="状态" prop="status"><el-select v-model="queryParams.status" clearable placeholder="全部状态" class="filter-control"><el-option v-for="item in statusOptions" :key="item.value" :label="item.label" :value="item.value" /></el-select></el-form-item> <template #filters>
<el-form-item label="单位" prop="unitId"><el-select v-model="queryParams.unitId" clearable filterable placeholder="全部单位" class="filter-control"><el-option v-for="item in unitOptions" :key="item.id" :label="item.label" :value="item.id" /></el-select></el-form-item> <el-form
ref="queryForm"
:model="queryParams"
size="small"
:inline="true"
class="data-list-filter-form"
>
<el-form-item label="状态" prop="status">
<el-select v-model="queryParams.status" clearable placeholder="全部状态">
<el-option
v-for="item in statusOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item label="单位" prop="unitId">
<el-select v-model="queryParams.unitId" clearable filterable placeholder="全部单位">
<el-option
v-for="item in unitOptions"
:key="item.id"
:label="item.label"
:value="item.id"
/>
</el-select>
</el-form-item>
<!-- 隐患名称筛选暂时隐藏,保留 queryParams 以便后续恢复。 --> <!-- 隐患名称筛选暂时隐藏,保留 queryParams 以便后续恢复。 -->
<!-- <el-form-item label="隐患名称" prop="hazardName"><el-input v-model.trim="queryParams.hazardName" clearable placeholder="请输入隐患名称" class="name-control" @keyup.enter.native="handleQuery" /></el-form-item> --> <el-form-item>
<el-form-item><el-button type="primary" icon="el-icon-search" @click="handleQuery">查询</el-button><el-button icon="el-icon-refresh" @click="resetQuery">重置</el-button></el-form-item> <el-button type="primary" icon="el-icon-search" size="small" @click="handleQuery">查询</el-button>
<div class="summary-inline"><span>当前筛选 <b>{{ total }}</b> 条</span><span>本页待审核 <b class="warning-text">{{ summary.pending }}</b></span><span>本页整改中 <b class="warning-text">{{ summary.rectifying }}</b></span><span>本页已闭环 <b class="success-text">{{ summary.closed }}</b></span><span>本页已驳回 <b class="danger-text">{{ summary.rejected }}</b></span></div> <el-button icon="el-icon-refresh" size="small" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form> </el-form>
<el-card shadow="never" class="table-card"> </template>
<el-table v-loading="loading" :data="ledgerList" stripe empty-text="暂无隐患台账数据" class="ledger-table">
<el-table-column prop="reportNo" label="隐患编号" min-width="150" show-overflow-tooltip /><el-table-column prop="hazardName" label="隐患名称" min-width="190" show-overflow-tooltip /> <template #actions>
<el-table-column label="分级" width="100" align="center"><template slot-scope="scope"><el-tag :type="levelTagType(scope.row)" size="small">{{ levelLabel(scope.row) }}</el-tag></template></el-table-column> <div class="summary-inline">
<el-table-column label="状态" width="110" align="center"><template slot-scope="scope"><el-tag :type="statusTagType(scope.row)" size="small">{{ statusLabel(scope.row) }}</el-tag></template></el-table-column> <span>当前筛选 <b>{{ total }}</b> 条</span>
<el-table-column label="积分发放" min-width="130" align="center"><template slot-scope="scope"><el-tag :type="pointsTagType(scope.row)" size="small">{{ pointsLabel(scope.row) }}</el-tag></template></el-table-column> <span>本页待提交 <b>{{ summary.draft }}</b></span>
<el-table-column label="平台同步" min-width="120" align="center"><template slot-scope="scope"><el-tag :type="pushTagType(scope.row)" size="small">{{ pushLabel(scope.row) }}</el-tag></template></el-table-column> <span>本页审核中 <b class="primary-text">{{ summary.reviewing }}</b></span>
<el-table-column prop="reporterName" label="上报人" width="100" show-overflow-tooltip /><el-table-column label="时间" width="145"><template slot-scope="scope">{{ formatTime(scope.row.createTime || scope.row.discoverTime) }}</template></el-table-column> <span>本页整改中 <b class="warning-text">{{ summary.rectifying }}</b></span>
<el-table-column label="操作" width="210" fixed="right" align="center"><template slot-scope="scope"><el-button type="text" size="small" @click="openDetail(scope.row)">详情</el-button><el-button v-if="isPending(scope.row)" type="primary" size="mini" @click="goAudit(scope.row)">去审核</el-button><el-button v-if="isSyncFailed(scope.row)" type="text" size="small" class="retry-button" @click="handleRetrySync(scope.row)">重试同步</el-button></template></el-table-column> <span>本页已通过 <b class="success-text">{{ summary.approved }}</b></span>
<span>本页已驳回 <b class="danger-text">{{ summary.rejected }}</b></span>
</div>
</template>
<el-table
v-loading="loading"
:data="ledgerList"
border
empty-text="暂无隐患台账数据"
class="data-list-table ledger-table"
>
<el-table-column prop="reportNo" label="隐患编号" min-width="150" show-overflow-tooltip />
<el-table-column prop="hazardName" label="隐患名称" min-width="190" show-overflow-tooltip />
<el-table-column label="分级" width="100" align="center">
<template slot-scope="scope">
<el-tag :type="levelTagType(scope.row)" size="small">{{ levelLabel(scope.row) }}</el-tag>
</template>
</el-table-column>
<el-table-column label="状态" width="110" align="center">
<template slot-scope="scope">
<el-tag :type="statusTagType(scope.row)" size="small">{{ statusLabel(scope.row) }}</el-tag>
</template>
</el-table-column>
<el-table-column label="积分发放" min-width="130" align="center">
<template slot-scope="scope">
<el-tag :type="pointsTagType(scope.row)" size="small">{{ pointsLabel(scope.row) }}</el-tag>
</template>
</el-table-column>
<!-- 平台同步列暂时隐藏,后续需要时可直接恢复。 -->
<!-- <el-table-column label="平台同步" min-width="120" align="center">
<template slot-scope="scope">
<el-tag :type="pushTagType(scope.row)" size="small">{{ pushLabel(scope.row) }}</el-tag>
</template>
</el-table-column> -->
<el-table-column prop="reporterName" label="上报人" width="100" show-overflow-tooltip />
<el-table-column label="时间" width="145">
<template slot-scope="scope">{{ formatTime(scope.row.createTime || scope.row.discoverTime) }}</template>
</el-table-column>
<el-table-column
label="操作"
width="210"
fixed="right"
align="center"
class-name="data-list-operation-column"
>
<template slot-scope="scope">
<el-button size="mini" @click="openDetail(scope.row)">详情</el-button>
<el-button v-if="isReviewing(scope.row)" type="primary" size="mini" @click="goAudit(scope.row)">去审核</el-button>
<el-button v-if="isSyncFailed(scope.row)" type="warning" size="mini" @click="handleRetrySync(scope.row)">重试同步</el-button>
</template>
</el-table-column>
</el-table> </el-table>
<div class="table-note">隐患状态与员工端一致:待审核 → 整改中 → 已闭环(审批通过即发积分)/ 已驳回。整改闭环由生产运营平台负责,不在本系统范围。</div>
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList" /> <template #footer>
</el-card> <pagination
v-show="total > 0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
<div class="table-note">
隐患状态:待提交 → 审核中 → 整改中 → 已通过 / 已驳回。整改闭环由生产运营平台负责,不在本系统范围。
</div>
</template>
</data-list-panel>
<el-dialog title="隐患详情" :visible.sync="detailVisible" width="980px" top="5vh" append-to-body> <el-dialog title="隐患详情" :visible.sync="detailVisible" width="980px" top="5vh" append-to-body>
<div v-loading="detailLoading" class="detail-dialog"><template v-if="detail"><div class="detail-head"><div><span class="detail-no">{{ detail.reportNo || '暂无编号' }}</span><span class="detail-name">{{ detail.hazardName || '未命名隐患' }}</span></div><div class="detail-tags"><el-tag :type="levelTagType(detail)" size="small">{{ levelLabel(detail) }}</el-tag><el-tag :type="statusTagType(detail)" size="small">{{ statusLabel(detail) }}</el-tag></div></div><el-descriptions title="基础信息" :column="3" border size="small"><el-descriptions-item v-for="item in basicFields" :key="item.label" :label="item.label">{{ displayField(detail, item) }}</el-descriptions-item></el-descriptions><el-descriptions title="整改与流程" :column="3" border size="small" class="detail-section"><el-descriptions-item v-for="item in flowFields" :key="item.label" :label="item.label">{{ displayField(detail, item) }}</el-descriptions-item></el-descriptions><div class="text-section"><div class="section-title">描述与处理</div><div v-for="item in textFields" :key="item.label" class="text-row"><span>{{ item.label }}</span><p>{{ detail[item.key] || '—' }}</p></div></div><div class="attachment-section"><div class="section-title">附件({{ attachments.length }})</div><el-tag v-for="item in attachments" :key="item.id || item.filePath" size="small" effect="plain" class="attachment-tag">{{ item.fileName || '附件' }}</el-tag><span v-if="!attachments.length" class="muted">暂无附件</span></div></template><el-empty v-else-if="!detailLoading" description="暂无详情数据" /></div> <div v-loading="detailLoading" class="detail-dialog">
<span slot="footer"><el-button @click="detailVisible = false">关闭</el-button><el-button v-if="detail && isPending(detail)" type="primary" @click="goAudit(detail)">去审核</el-button></span> <template v-if="detail">
<div class="detail-head">
<div>
<span class="detail-no">{{ detail.reportNo || '暂无编号' }}</span>
<span class="detail-name">{{ detail.hazardName || '未命名隐患' }}</span>
</div>
<div class="detail-tags">
<el-tag :type="levelTagType(detail)" size="small">{{ levelLabel(detail) }}</el-tag>
<el-tag :type="statusTagType(detail)" size="small">{{ statusLabel(detail) }}</el-tag>
</div>
</div>
<el-descriptions title="基础信息" :column="3" border size="small">
<el-descriptions-item v-for="item in basicFields" :key="item.label" :label="item.label">{{ displayField(detail, item) }}</el-descriptions-item>
</el-descriptions>
<el-descriptions title="整改与流程" :column="3" border size="small" class="detail-section">
<el-descriptions-item v-for="item in flowFields" :key="item.label" :label="item.label">{{ displayField(detail, item) }}</el-descriptions-item>
</el-descriptions>
<div class="text-section">
<div class="section-title">描述与处理</div>
<div v-for="item in textFields" :key="item.label" class="text-row">
<span>{{ item.label }}</span>
<p>{{ detail[item.key] || '—' }}</p>
</div>
</div>
<div class="attachment-section">
<div class="section-title">附件({{ attachments.length }})</div>
<div v-if="attachments.length" class="attachment-grid">
<el-image
v-for="(item, index) in imageAttachments"
:key="attachmentKey(item, index)"
:src="attachmentUrl(item)"
:preview-src-list="previewUrls"
:initial-index="previewIndex(item)"
fit="cover"
class="attachment-image"
>
<div slot="error" class="attachment-placeholder">
<i class="el-icon-picture-outline" />
<span>{{ item.fileName || '图片加载失败' }}</span>
</div>
</el-image>
<a
v-for="(item, index) in otherAttachments"
:key="attachmentKey(item, index)"
:href="attachmentUrl(item) || undefined"
:target="attachmentUrl(item) ? '_blank' : undefined"
rel="noopener noreferrer"
class="attachment-file"
>
<i class="el-icon-document" />
<span>{{ item.fileName || '附件' }}</span>
</a>
</div>
<span v-else class="muted">暂无附件</span>
</div>
</template>
<el-empty v-else-if="!detailLoading" description="暂无详情数据" />
</div>
<span slot="footer">
<el-button @click="detailVisible = false">关闭</el-button>
<el-button v-if="detail && isReviewing(detail)" type="primary" @click="goAudit(detail)">去审核</el-button>
</span>
</el-dialog> </el-dialog>
</div> </div>
</template> </template>
<script> <script>
/* global MyConfig */
import { deptTreeSelect } from '@/api/system/user' import { deptTreeSelect } from '@/api/system/user'
import { listHazardLedger, getHazardLedgerDetail, listHazardLedgerAttachments, retryHazardSync } from '@/api/hazard/ledger' import { listHazardLedger, getHazardLedgerDetail, listHazardLedgerAttachments, retryHazardSync } from '@/api/hazard/ledger'
export default { export default {
name: 'HazardLedger', name: 'HazardLedger',
data() { return { loading: false, detailLoading: false, detailVisible: false, ledgerList: [], total: 0, unitOptions: [], attachments: [], detail: null, queryParams: { pageNum: 1, pageSize: 10, status: undefined, unitId: undefined, hazardName: undefined }, statusOptions: [{ label: '待审核', value: '1' }, { label: '整改中', value: '2' }, { label: '已闭环', value: '3' }, { label: '已驳回', value: '4' }], basicFields: [{ label: '单位', key: 'unitName' }, { label: '所属部门', key: 'departmentName', fallback: 'factoryDeptName' }, { label: '上报人', key: 'reporterName' }, { label: '隐患分类', key: 'hazardCategoryName', fallback: 'hazardCategory' }, { label: '隐患类型', key: 'hazardTypeName', fallback: 'hazardType' }, { label: '隐患来源', key: 'sourceTypeName', fallback: 'sourceType' }, { label: '专业', key: 'majorName', fallback: 'major' }, { label: '风险分析单元', key: 'riskUnit' }, { label: '风险分析对象', key: 'riskTarget' }, { label: '发现时间', key: 'discoverTime', time: true }, { label: '创建时间', key: 'createTime', time: true }, { label: '更新人', key: 'updateBy' }], flowFields: [{ label: '治理类型', key: 'governanceTypeName', fallback: 'governanceType' }, { label: '整改负责人', key: 'chargePersonName' }, { label: '限定完成时间', key: 'limitedCompleteTime', time: true }, { label: '实际完成时间', key: 'actualCompleteTime', time: true }, { label: '奖励积分', key: 'pointsAwarded', suffix: ' 分' }, { label: '积分状态', key: 'pointsStatusName', fallback: 'pointsStatus' }, { label: '平台同步', key: 'pushStatusName', fallback: 'pushStatus' }, { label: '同步时间', key: 'pushTime', time: true }, { label: '第三方记录', key: 'thirdPartyId' }, { label: '流程状态', key: 'flowStatus' }, { label: '审核人', key: 'auditUserNameName', fallback: 'auditUserName' }, { label: '审核时间', key: 'auditTime', time: true }], textFields: [{ label: '隐患描述', key: 'description' }, { label: '原因分析', key: 'causeAnalysis' }, { label: '整改方案', key: 'rectificationPlan' }, { label: '防范措施', key: 'preventiveMeasures' }, { label: '审核意见', key: 'auditRemark' }, { label: '备注', key: 'remark' }] } }, data() { return { loading: false, detailLoading: false, detailVisible: false, ledgerList: [], total: 0, unitOptions: [], attachments: [], detail: null, queryParams: { pageNum: 1, pageSize: 10, status: undefined, unitId: undefined, hazardName: undefined }, statusOptions: [{ label: '待提交', value: 'draft' }, { label: '审核中', value: 'reviewing' }, { label: '整改中', value: 'rectifying' }, { label: '已通过', value: 'approved' }, { label: '已驳回', value: 'rejected' }], basicFields: [{ label: '单位', key: 'unitName' }, { label: '所属部门', key: 'departmentName', fallback: 'factoryDeptName' }, { label: '上报人', key: 'reporterName' }, { label: '隐患分类', key: 'hazardCategoryName', fallback: 'hazardCategory' }, { label: '隐患类型', key: 'hazardTypeName', fallback: 'hazardType' }, { label: '隐患来源', key: 'sourceTypeName', fallback: 'sourceType' }, { label: '专业', key: 'majorName', fallback: 'major' }, { label: '风险分析单元', key: 'riskUnit' }, { label: '风险分析对象', key: 'riskTarget' }, { label: '发现时间', key: 'discoverTime', time: true }, { label: '创建时间', key: 'createTime', time: true }, { label: '更新人', key: 'updateBy' }], flowFields: [{ label: '治理类型', key: 'governanceTypeName', fallback: 'governanceType' }, { label: '整改负责人', key: 'chargePersonName' }, { label: '限定完成时间', key: 'limitedCompleteTime', time: true }, { label: '实际完成时间', key: 'actualCompleteTime', time: true }, { label: '奖励积分', key: 'pointsAwarded', suffix: ' 分' }, { label: '积分状态', key: 'pointsStatusName', fallback: 'pointsStatus' }, { label: '平台同步', key: 'pushStatusName', fallback: 'pushStatus' }, { label: '同步时间', key: 'pushTime', time: true }, { label: '第三方记录', key: 'thirdPartyId' }, { label: '流程状态', key: 'flowStatus' }, { label: '审核人', key: 'auditUserNameName', fallback: 'auditUserName' }, { label: '审核时间', key: 'auditTime', time: true }], textFields: [{ label: '隐患描述', key: 'description' }, { label: '原因分析', key: 'causeAnalysis' }, { label: '整改方案', key: 'rectificationPlan' }, { label: '防范措施', key: 'preventiveMeasures' }, { label: '审核意见', key: 'auditRemark' }, { label: '备注', key: 'remark' }] } },
computed: { summary() { return this.ledgerList.reduce((result, row) => { const status = this.statusKey(row); if (status === '1') result.pending++; else if (status === '2') result.rectifying++; else if (status === '3') result.closed++; else if (status === '4') result.rejected++; return result }, { pending: 0, rectifying: 0, closed: 0, rejected: 0 }) } }, computed: {
summary() { return this.ledgerList.reduce((result, row) => { const status = this.statusKey(row); if (status === 'draft') result.draft++; else if (status === 'reviewing') result.reviewing++; else if (status === 'rectifying') result.rectifying++; else if (status === 'approved') result.approved++; else if (status === 'rejected') result.rejected++; return result }, { draft: 0, reviewing: 0, rectifying: 0, approved: 0, rejected: 0 }) },
imageAttachments() { return this.attachments.filter(item => this.isImageAttachment(item)) },
otherAttachments() { return this.attachments.filter(item => !this.isImageAttachment(item)) },
previewUrls() { return this.imageAttachments.map(item => this.attachmentUrl(item)).filter(Boolean) }
},
created() { this.getList(); this.getUnits() }, created() { this.getList(); this.getUnits() },
methods: { methods: {
getList() { this.loading = true; listHazardLedger(this.queryParams).then(response => { const page = response.data || response; this.ledgerList = page.list || page.rows || []; this.total = Number(page.total || page.totalCount || this.ledgerList.length) }).catch(() => { this.ledgerList = []; this.total = 0; this.$modal.msgError('隐患台账加载失败,请检查后端服务和登录状态') }).finally(() => { this.loading = false }) }, getList() { this.loading = true; listHazardLedger(this.queryParams).then(response => { const page = response.data || response; this.ledgerList = page.list || page.rows || []; this.total = Number(page.total || page.totalCount || this.ledgerList.length) }).catch(() => { this.ledgerList = []; this.total = 0; this.$modal.msgError('隐患台账加载失败,请检查后端服务和登录状态') }).finally(() => { this.loading = false }) },
@ -44,12 +200,206 @@ export default {
openDetail(row) { this.detailVisible = true; this.detail = null; this.attachments = []; this.detailLoading = true; Promise.all([getHazardLedgerDetail(row.id), listHazardLedgerAttachments(row.id)]).then(results => { this.detail = results[0].data || results[0]; const data = results[1].data || results[1]; this.attachments = Array.isArray(data) ? data : [] }).catch(() => { this.$modal.msgError('隐患详情加载失败') }).finally(() => { this.detailLoading = false }) }, openDetail(row) { this.detailVisible = true; this.detail = null; this.attachments = []; this.detailLoading = true; Promise.all([getHazardLedgerDetail(row.id), listHazardLedgerAttachments(row.id)]).then(results => { this.detail = results[0].data || results[0]; const data = results[1].data || results[1]; this.attachments = Array.isArray(data) ? data : [] }).catch(() => { this.$modal.msgError('隐患详情加载失败') }).finally(() => { this.detailLoading = false }) },
goAudit(row) { const taskId = row.taskId || row.taskID || row.todoTaskId; if (!taskId) { this.$modal.msgWarning('当前记录缺少任务 ID,无法进入审核'); return } this.detailVisible = false; this.$router.push({ path: '/hazard/audit/detail', query: { id: String(row.id), taskId: String(taskId) }}) }, goAudit(row) { const taskId = row.taskId || row.taskID || row.todoTaskId; if (!taskId) { this.$modal.msgWarning('当前记录缺少任务 ID,无法进入审核'); return } this.detailVisible = false; this.$router.push({ path: '/hazard/audit/detail', query: { id: String(row.id), taskId: String(taskId) }}) },
handleRetrySync(row) { this.$confirm('确认重试将该隐患同步到生产运营平台吗?', '重试同步', { type: 'warning' }).then(() => retryHazardSync(row.id)).then(() => { this.$modal.msgSuccess('同步重试成功'); this.getList() }).catch(error => { if (error !== 'cancel') this.$modal.msgError(error.message || '同步重试失败') }) }, handleRetrySync(row) { this.$confirm('确认重试将该隐患同步到生产运营平台吗?', '重试同步', { type: 'warning' }).then(() => retryHazardSync(row.id)).then(() => { this.$modal.msgSuccess('同步重试成功'); this.getList() }).catch(error => { if (error !== 'cancel') this.$modal.msgError(error.message || '同步重试失败') }) },
statusKey(row) { const value = String(row.status || ''); if (/待审核/.test(row.statusName || '') || value === '1') return '1'; if (/整改中/.test(row.statusName || '') || value === '2') return '2'; if (/闭环|已通过/.test(row.statusName || '') || value === '3') return '3'; if (/驳回/.test(row.statusName || '') || value === '4') return '4'; return value }, isPending(row) { return this.statusKey(row) === '1' }, isSyncFailed(row) { return /失败|异常/.test(row.pushStatusName || row.pushStatus || '') }, levelLabel(row) { return row.hazardLevelName || ({ general: '一般', major: '较大', critical: '重大' }[row.hazardLevel] || row.hazardLevel || '—') }, statusLabel(row) { return row.statusName || ({ 0: '待提交', 1: '待审核', 2: '整改中', 3: '已闭环', 4: '已驳回' }[String(row.status)] || row.status || '—') }, pointsLabel(row) { if (row.pointsStatusName) return row.pointsAwarded === undefined || row.pointsAwarded === null ? row.pointsStatusName : `${row.pointsAwarded > 0 ? '+' : ''}${row.pointsAwarded} ${row.pointsStatusName}`; return row.pointsAwarded === undefined || row.pointsAwarded === null ? '—' : `${row.pointsAwarded > 0 ? '+' : ''}${row.pointsAwarded} 分` }, pushLabel(row) { return row.pushStatusName || ({ SUCCESS: '已同步', FAIL: '同步失败', FAILED: '同步失败', WAIT: '待同步' }[String(row.pushStatus).toUpperCase()] || row.pushStatus || '—') }, statusTagType(row) { const value = this.statusKey(row); return value === '3' ? 'success' : value === '4' ? 'danger' : value === '1' || value === '2' ? 'warning' : 'info' }, levelTagType(row) { return String(row.hazardLevel || '').toLowerCase() === 'critical' ? 'danger' : String(row.hazardLevel || '').toLowerCase() === 'major' ? 'warning' : 'info' }, pointsTagType(row) { return row.pointsStatusName && /待|未/.test(row.pointsStatusName) ? 'info' : row.pointsAwarded === undefined || row.pointsAwarded === null ? 'info' : 'success' }, pushTagType(row) { return this.isSyncFailed(row) ? 'danger' : /待/.test(this.pushLabel(row)) ? 'info' : 'success' }, statusKey(row) { const value = String(row.status || '').toLowerCase(); const name = row.statusName || ''; if (/待提交|未提交/.test(name) || value === 'draft' || value === '0') return 'draft'; if (/审核中|待审核/.test(name) || value === 'reviewing' || value === '1') return 'reviewing'; if (/整改中/.test(name) || value === 'rectifying' || value === '2') return 'rectifying'; if (/已通过|已闭环/.test(name) || value === 'approved' || value === '3') return 'approved'; if (/已驳回|驳回/.test(name) || value === 'rejected' || value === '4') return 'rejected'; return value }, isReviewing(row) { return this.statusKey(row) === 'reviewing' }, isSyncFailed(row) { return /失败|异常/.test(row.pushStatusName || row.pushStatus || '') }, levelLabel(row) { return row.hazardLevelName || ({ general: '一般', major: '较大', critical: '重大' }[row.hazardLevel] || row.hazardLevel || '—') }, statusLabel(row) { const key = this.statusKey(row); return ({ draft: '待提交', reviewing: '审核中', rectifying: '整改中', approved: '已通过', rejected: '已驳回' }[key] || row.statusName || row.status || '—') }, pointsLabel(row) { if (row.pointsStatusName) return row.pointsAwarded === undefined || row.pointsAwarded === null ? row.pointsStatusName : `${row.pointsAwarded > 0 ? '+' : ''}${row.pointsAwarded} ${row.pointsStatusName}`; return row.pointsAwarded === undefined || row.pointsAwarded === null ? '—' : `${row.pointsAwarded > 0 ? '+' : ''}${row.pointsAwarded} 分` }, pushLabel(row) { return row.pushStatusName || ({ SUCCESS: '已同步', FAIL: '同步失败', FAILED: '同步失败', WAIT: '待同步' }[String(row.pushStatus).toUpperCase()] || row.pushStatus || '—') }, statusTagType(row) { const value = this.statusKey(row); return value === 'approved' ? 'success' : value === 'rejected' ? 'danger' : value === 'rectifying' ? 'warning' : value === 'reviewing' ? 'primary' : 'info' }, levelTagType(row) { return String(row.hazardLevel || '').toLowerCase() === 'critical' ? 'danger' : String(row.hazardLevel || '').toLowerCase() === 'major' ? 'warning' : 'info' }, pointsTagType(row) { return row.pointsStatusName && /待|未/.test(row.pointsStatusName) ? 'info' : row.pointsAwarded === undefined || row.pointsAwarded === null ? 'info' : 'success' }, pushTagType(row) { return this.isSyncFailed(row) ? 'danger' : /待/.test(this.pushLabel(row)) ? 'info' : 'success' },
isImageAttachment(item) { return String(item.attachmentType || '').toUpperCase() === 'IMAGE' || /^image\//i.test(item.fileType || '') || /\.(png|jpe?g|gif|webp|bmp|svg)(?:\?.*)?$/i.test(item.fileName || item.filePath || item.fileUrl || '') },
attachmentUrl(item) { const path = item.fileUrl || item.filePath || ''; if (!path || /^https?:\/\//i.test(path) || /^data:image\//i.test(path)) return path; return MyConfig.BASE_API.replace(/\/$/, '') + '/' + path.replace(/^\//, '') },
previewIndex(item) { return this.previewUrls.indexOf(this.attachmentUrl(item)) },
attachmentKey(item, index) { return item.id || item.fileUrl || item.filePath || `attachment-${index}` },
displayField(row, item) { const value = row[item.key] || (item.fallback && row[item.fallback]); if (value === undefined || value === null || value === '') return '—'; return (item.time ? this.formatTime(value) : String(value)) + (item.suffix || '') }, formatTime(value) { return value ? String(value).replace('T', ' ').slice(0, 19) : '—' } displayField(row, item) { const value = row[item.key] || (item.fallback && row[item.fallback]); if (value === undefined || value === null || value === '') return '—'; return (item.time ? this.formatTime(value) : String(value)) + (item.suffix || '') }, formatTime(value) { return value ? String(value).replace('T', ' ').slice(0, 19) : '—' }
} }
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.hazard-ledger { min-height: calc(100vh - 52px); background: #f0f2f5; }.filter-bar { display: flex; align-items: center; flex-wrap: wrap; margin-bottom: 14px; padding: 14px 16px 0; background: #fff; border: 1px solid #f0f0f2; border-radius: 8px; }.filter-control { width: 170px; }.name-control { width: 210px; }.summary-inline { display: flex; align-items: center; gap: 20px; margin: 0 0 14px auto; padding: 0 6px; color: #9898ae; font-size: 12px; white-space: nowrap; }.summary-inline b { margin-left: 3px; color: #1a1a2e; }.warning-text { color: #c97e24 !important; }.success-text { color: #2d8a4e !important; }.danger-text { color: #d64550 !important; }.table-card { border: 1px solid #f0f0f2; border-radius: 8px; }.table-note { margin-top: 12px; color: #9898ae; font-size: 11px; line-height: 1.5; }.retry-button { color: #c97e24; }.detail-dialog { min-height: 250px; max-height: 68vh; overflow-y: auto; padding: 2px 2px 8px; }.detail-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 14px; }.detail-no { margin-right: 14px; color: #9898ae; font-size: 12px; }.detail-name { color: #1a1a2e; font-size: 16px; font-weight: 600; }.detail-tags { display: flex; gap: 8px; }.detail-section, .text-section, .attachment-section { margin-top: 16px; }.section-title { margin-bottom: 8px; color: #5a5a72; font-size: 13px; font-weight: 600; }.text-row { display: grid; grid-template-columns: 90px 1fr; gap: 10px; padding: 7px 0; border-bottom: 1px solid #f0f0f2; font-size: 12px; }.text-row > span { color: #9898ae; }.text-row p { margin: 0; color: #1a1a2e; white-space: pre-wrap; word-break: break-word; }.attachment-tag { margin: 0 8px 8px 0; }.muted { color: #9898ae; font-size: 12px; }@media (max-width: 800px) { .summary-inline { width: 100%; justify-content: flex-end; flex-wrap: wrap; gap: 8px 16px; margin: 0 0 10px; white-space: normal; }.filter-control, .name-control { width: 100%; }.filter-bar .el-form-item { width: 100%; margin-right: 0; } } .hazard-ledger {
min-height: calc(100vh - 52px);
background: #f0f2f5;
}
.summary-inline {
display: flex;
align-items: center;
gap: 20px;
color: var(--prototype-text-secondary, #6b7280);
font-size: 12px;
white-space: nowrap;
}
.summary-inline b {
margin-left: 3px;
color: var(--prototype-text, #1a1a2e);
}
.warning-text {
color: #c97e24 !important;
}
.primary-text {
color: #409eff !important;
}
.success-text {
color: #2d8a4e !important;
}
.danger-text {
color: #d64550 !important;
}
.table-note {
margin-top: 10px;
padding: 9px 12px;
color: var(--prototype-text-secondary, #6b7280);
font-size: 12px;
line-height: 20px;
}
.detail-dialog {
min-height: 250px;
max-height: 68vh;
overflow-y: auto;
padding: 2px 2px 8px;
}
.detail-head {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 14px;
}
.detail-no {
margin-right: 14px;
color: #9898ae;
font-size: 12px;
}
.detail-name {
color: #1a1a2e;
font-size: 16px;
font-weight: 600;
}
.detail-tags {
display: flex;
gap: 8px;
}
.detail-section,
.text-section,
.attachment-section {
margin-top: 16px;
}
.section-title {
margin-bottom: 8px;
color: #5a5a72;
font-size: 13px;
font-weight: 600;
}
.text-row {
display: grid;
grid-template-columns: 90px 1fr;
gap: 10px;
padding: 7px 0;
border-bottom: 1px solid #f0f0f2;
font-size: 12px;
}
.text-row > span {
color: #9898ae;
}
.text-row p {
margin: 0;
color: #1a1a2e;
white-space: pre-wrap;
word-break: break-word;
}
.attachment-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 10px;
}
.attachment-image,
.attachment-file {
width: 100%;
height: 150px;
overflow: hidden;
border: 1px solid #e4e7ed;
border-radius: 6px;
background: #f7f9fc;
}
.attachment-image {
cursor: zoom-in;
}
.attachment-file,
.attachment-placeholder {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 8px;
padding: 12px;
color: #909399;
text-align: center;
text-decoration: none;
}
.attachment-file {
font-size: 13px;
}
.attachment-file:hover {
border-color: #409eff;
color: #409eff;
}
.attachment-file i,
.attachment-placeholder i {
font-size: 28px;
}
.attachment-file span,
.attachment-placeholder span {
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.attachment-placeholder {
width: 100%;
height: 100%;
font-size: 12px;
}
.muted {
color: #9898ae;
font-size: 12px;
}
@media (max-width: 800px) {
.summary-inline {
width: 100%;
flex-wrap: wrap;
gap: 8px 16px;
white-space: normal;
}
.attachment-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@media (max-width: 520px) {
.attachment-grid {
grid-template-columns: 1fr;
}
}
</style> </style>

2
src/views/login.vue

@ -35,7 +35,7 @@
<el-checkbox v-model="loginForm.rememberMe" style="margin:0px 0px 25px 0px; color: #fff;">记住密码</el-checkbox> <el-checkbox v-model="loginForm.rememberMe" style="margin:0px 0px 25px 0px; color: #fff;">记住密码</el-checkbox>
<el-button :loading="loading" @click="handleLogin" class="login_btn" type="primary" round size="large">登 陆</el-button> <el-button :loading="loading" @click="handleLogin" class="login_btn" type="primary" round size="large">登 录</el-button>
<div class="loginguang"></div> <div class="loginguang"></div>
</div> </div>

101
src/views/points/management/index.vue

@ -66,7 +66,6 @@
<template #actions> <template #actions>
<div class="data-list-actions"> <div class="data-list-actions">
<el-button <el-button
v-if="isSuperAdmin"
type="primary" type="primary"
size="small" size="small"
icon="el-icon-plus" icon="el-icon-plus"
@ -96,6 +95,15 @@
</el-tag> </el-tag>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column
label="厂区"
min-width="160"
show-overflow-tooltip
>
<template slot-scope="scope">
{{ factoryLabel(scope.row) }}
</template>
</el-table-column>
<el-table-column label="档位" width="120" align="center"> <el-table-column label="档位" width="120" align="center">
<template slot-scope="scope"> <template slot-scope="scope">
<span class="tier-text">{{ tierLabel(scope.row) }}</span> <span class="tier-text">{{ tierLabel(scope.row) }}</span>
@ -170,7 +178,7 @@
append-to-body append-to-body
><el-descriptions v-if="detail" :column="2" border size="small" ><el-descriptions v-if="detail" :column="2" border size="small"
><el-descriptions-item label="厂区">{{ ><el-descriptions-item label="厂区">{{
detail.deptName || (detail.deptId ? detail.deptId : "全局规则") factoryLabel(detail)
}}</el-descriptions-item }}</el-descriptions-item
><el-descriptions-item label="隐患等级">{{ ><el-descriptions-item label="隐患等级">{{
levelLabel(detail) levelLabel(detail)
@ -217,16 +225,16 @@
><el-form-item label="所属厂区" prop="deptId" ><el-form-item label="所属厂区" prop="deptId"
><el-select ><el-select
v-model="addForm.deptId" v-model="addForm.deptId"
:disabled="!isSuperAdmin"
:loading="factoryLoading"
filterable filterable
placeholder="请选择厂区" placeholder="请选择厂区"
style="width: 100%" style="width: 100%"
><el-option ><el-option
v-for="item in factoryOptions" v-for="item in displayedFactoryOptions"
:key="String(item.deptId || item.id)" :key="factoryId(item)"
:label="item.deptName || item.label || item.name" :label="factoryName(item)"
:value=" :value="factoryId(item)" /></el-select></el-form-item
String(item.deptId || item.id)
" /></el-select></el-form-item
><el-form-item label="隐患等级" prop="hazardLevel" ><el-form-item label="隐患等级" prop="hazardLevel"
><el-select ><el-select
v-model="addForm.hazardLevel" v-model="addForm.hazardLevel"
@ -258,7 +266,9 @@
style="width: 100%" /></el-form-item style="width: 100%" /></el-form-item
><el-form-item label="规则编码" ><el-form-item label="规则编码"
><el-input v-model.trim="addForm.ruleCode" /></el-form-item ><el-input v-model.trim="addForm.ruleCode" /></el-form-item
><el-form-item label="规则名称" ><el-form-item
label="规则名称"
prop="ruleName"
><el-input v-model.trim="addForm.ruleName" /></el-form-item ><el-input v-model.trim="addForm.ruleName" /></el-form-item
><el-form-item label="备注" ><el-form-item label="备注"
><el-input ><el-input
@ -267,7 +277,11 @@
:rows="2" /></el-form-item></el-form :rows="2" /></el-form-item></el-form
><span slot="footer" ><span slot="footer"
><el-button @click="addVisible = false">取消</el-button ><el-button @click="addVisible = false">取消</el-button
><el-button type="primary" :loading="saving" @click="submitAdd" ><el-button
type="primary"
:loading="saving"
:disabled="factoryLoading || !canSubmitAdd"
@click="submitAdd"
>确定</el-button >确定</el-button
></span ></span
></el-dialog ></el-dialog
@ -278,6 +292,7 @@
import { import {
listPointsRules, listPointsRules,
listFactoryDepartments, listFactoryDepartments,
getMyFactoryDepartment,
togglePointsRule, togglePointsRule,
deletePointsRule, deletePointsRule,
getPointsRule, getPointsRule,
@ -298,10 +313,12 @@ export default {
return { return {
activeTab: "rules", activeTab: "rules",
loading: false, loading: false,
factoryLoading: false,
saving: false, saving: false,
total: 0, total: 0,
ruleList: [], ruleList: [],
factoryOptions: [], factoryOptions: [],
currentFactory: null,
detail: null, detail: null,
detailVisible: false, detailVisible: false,
addVisible: false, addVisible: false,
@ -337,6 +354,9 @@ export default {
}, },
], ],
points: [{ required: true, message: "请输入积分", trigger: "change" }], points: [{ required: true, message: "请输入积分", trigger: "change" }],
ruleName: [
{ required: true, message: '请输入规则名称', trigger: 'blur' }
]
}, },
}; };
}, },
@ -344,15 +364,24 @@ export default {
isSuperAdmin() { isSuperAdmin() {
return (this.$store.getters.roles || []).indexOf("admin") !== -1; return (this.$store.getters.roles || []).indexOf("admin") !== -1;
}, },
displayedFactoryOptions() {
if (this.isSuperAdmin) return this.factoryOptions;
return this.currentFactory ? [this.currentFactory] : [];
},
canSubmitAdd() {
return this.isSuperAdmin || Boolean(this.factoryId(this.currentFactory));
},
}, },
created() { created() {
this.getList(); this.getList();
if (this.isSuperAdmin) this.getFactories(); this.loadFactoryScope();
}, },
methods: { methods: {
getList() { getList() {
this.loading = true; this.loading = true;
listPointsRules(this.queryParams) const params = Object.assign({}, this.queryParams);
if (!this.isSuperAdmin) delete params.deptId;
listPointsRules(params)
.then((res) => { .then((res) => {
const page = res.data || res; const page = res.data || res;
this.ruleList = this.sortRules(page.list || page.rows || []); this.ruleList = this.sortRules(page.list || page.rows || []);
@ -369,16 +398,32 @@ export default {
this.loading = false; this.loading = false;
}); });
}, },
getFactories() { loadFactoryScope() {
listFactoryDepartments() this.factoryLoading = true;
const request = this.isSuperAdmin
? listFactoryDepartments()
: getMyFactoryDepartment();
request
.then((res) => { .then((res) => {
const data = res.data || res; const data = res.data || res;
if (this.isSuperAdmin) {
this.factoryOptions = Array.isArray(data) this.factoryOptions = Array.isArray(data)
? data ? data
: data.list || data.rows || []; : data.list || data.rows || [];
return;
}
this.currentFactory = data || null;
if (this.addVisible) {
this.addForm.deptId = this.factoryId(this.currentFactory);
}
}) })
.catch(() => { .catch(() => {
this.factoryOptions = []; this.factoryOptions = [];
this.currentFactory = null;
this.$modal.msgError("厂区信息加载失败");
})
.finally(() => {
this.factoryLoading = false;
}); });
}, },
sortRules(rows) { sortRules(rows) {
@ -407,7 +452,7 @@ export default {
}, },
openAdd() { openAdd() {
this.addForm = { this.addForm = {
deptId: "", deptId: this.isSuperAdmin ? "" : this.factoryId(this.currentFactory),
hazardLevel: "", hazardLevel: "",
hazardCategory: "", hazardCategory: "",
points: null, points: null,
@ -417,6 +462,7 @@ export default {
remark: "", remark: "",
}; };
this.addVisible = true; this.addVisible = true;
this.$nextTick(() => this.$refs.addForm.clearValidate());
}, },
suggestTier() { suggestTier() {
this.addForm.hazardCategory = this.nextTier(this.addForm.hazardLevel); this.addForm.hazardCategory = this.nextTier(this.addForm.hazardLevel);
@ -463,23 +509,29 @@ export default {
.toUpperCase(); .toUpperCase();
}, },
submitAdd() { submitAdd() {
if (!this.canSubmitAdd) {
this.$modal.msgError("当前厂区信息未加载,暂不能新增积分规则");
return;
}
this.$refs.addForm.validate((valid) => { this.$refs.addForm.validate((valid) => {
if (!valid) return; if (!valid) return;
const payload = { const payload = {
deptId: this.addForm.deptId,
hazardLevel: this.addForm.hazardLevel, hazardLevel: this.addForm.hazardLevel,
hazardCategory: this.addForm.hazardCategory, hazardCategory: this.addForm.hazardCategory,
points: this.addForm.points, points: this.addForm.points,
enabled: 1, enabled: 1,
priority: this.addForm.priority, priority: this.addForm.priority,
ruleCode: this.addForm.ruleCode || undefined, ruleCode: this.addForm.ruleCode || undefined,
ruleName: this.addForm.ruleName || undefined, ruleName: this.addForm.ruleName,
remark: this.addForm.remark || undefined, remark: this.addForm.remark || undefined,
}; };
if (this.isSuperAdmin) {
payload.deptId = this.addForm.deptId;
const item = this.factoryOptions.find( const item = this.factoryOptions.find(
(row) => String(row.deptId || row.id) === String(payload.deptId) (row) => this.factoryId(row) === String(payload.deptId)
); );
if (item) payload.deptName = item.deptName || item.label || item.name; if (item) payload.deptName = this.factoryName(item);
}
this.saving = true; this.saving = true;
addPointsRule(payload) addPointsRule(payload)
.then(() => { .then(() => {
@ -523,6 +575,17 @@ export default {
showPendingAction(name) { showPendingAction(name) {
this.$modal.msgInfo(`${name}功能暂未接入接口`); this.$modal.msgInfo(`${name}功能暂未接入接口`);
}, },
factoryId(item) {
const value =
item && item.deptId !== undefined ? item.deptId : item && item.id;
return value === undefined || value === null ? "" : String(value);
},
factoryName(item) {
return (item && (item.deptName || item.label || item.name)) || "";
},
factoryLabel(row) {
return this.factoryName(row) || this.factoryId(row) || "全局规则";
},
toEnabled(value) { toEnabled(value) {
return Number(value) === 1 ? 1 : 0; return Number(value) === 1 ? 1 : 0;
}, },

Loading…
Cancel
Save