| 123456789101112131415161718192021222324252627282930313233343536373839404142 |
- <template>
- <div class="inspect-page">
- <div class="head"><div><h2>门店巡检与考核</h2><p>指标、计划、任务、整改、排名闭环管理</p></div><el-button type="primary" icon="el-icon-refresh" @click="load">刷新</el-button></div>
- <el-tabs v-model="tab" type="card" @tab-click="load">
- <el-tab-pane label="考核指标" name="indicator">
- <section class="panel"><div class="toolbar"><el-input v-model="indicatorQuery.indicatorName" placeholder="指标名称" clearable/><el-input v-model="indicatorQuery.indicatorCode" placeholder="指标编码" clearable/><el-select v-model="indicatorQuery.indicatorType" placeholder="类型" clearable><el-option label="自动采集" :value="1"/><el-option label="人工巡检" :value="2"/></el-select><el-select v-model="indicatorQuery.status" placeholder="状态" clearable><el-option label="启用" :value="1"/><el-option label="停用" :value="0"/></el-select><el-button type="primary" @click="loadIndicators">查询</el-button><el-button @click="indicatorQuery={};loadIndicators()">重置</el-button><span class="grow"></span><el-button type="success" plain @click="checkWeight">权重校验</el-button><el-button type="primary" @click="openIndicator()">新增指标</el-button></div>
- <el-table :data="indicators" border stripe><el-table-column prop="sort" label="排序" width="70"/><el-table-column prop="indicatorCode" label="编码" width="140"/><el-table-column prop="indicatorName" label="名称"/><el-table-column label="类型" width="100"><template slot-scope="s"><el-tag size="mini" :type="s.row.indicatorType===1?'primary':'warning'">{{typeText(s.row.indicatorType)}}</el-tag></template></el-table-column><el-table-column prop="dataSource" label="数据来源"/><el-table-column prop="maxScore" label="满分" width="80"/><el-table-column prop="passScore" label="及格" width="80"/><el-table-column prop="weight" label="权重%" width="80"/><el-table-column label="状态" width="90"><template slot-scope="s"><el-switch v-model="s.row.status" :active-value="1" :inactive-value="0" @change="changeIndicator(s.row)"/></template></el-table-column><el-table-column label="操作" width="130"><template slot-scope="s"><el-button type="text" @click="openIndicator(s.row)">编辑</el-button><el-button type="text" class="danger" @click="removeIndicator(s.row)">删除</el-button></template></el-table-column></el-table></section>
- </el-tab-pane>
- <el-tab-pane label="巡检计划" name="plan">
- <section class="panel" v-if="planMode==='list'"><div class="toolbar"><el-input v-model="planQuery.planName" placeholder="计划名称" clearable/><el-select v-model="planQuery.planType" placeholder="类型" clearable><el-option label="常规巡检" :value="1"/><el-option label="专项巡检" :value="2"/><el-option label="突击检查" :value="3"/></el-select><el-select v-model="planQuery.status" placeholder="状态" clearable><el-option label="草稿" :value="0"/><el-option label="已发布" :value="1"/><el-option label="已暂停" :value="2"/><el-option label="已结束" :value="3"/></el-select><el-button type="primary" @click="loadPlans">查询</el-button><span class="grow"></span><el-button type="primary" @click="openPlan">创建巡检计划</el-button></div>
- <el-table :data="plans" border stripe><el-table-column prop="planName" label="计划"/><el-table-column label="类型"><template slot-scope="s">{{planType(s.row.planType)}}</template></el-table-column><el-table-column label="周期"><template slot-scope="s">{{cycle(s.row.cycleType)}}</template></el-table-column><el-table-column prop="storeCount" label="门店数"/><el-table-column prop="startDate" label="开始"/><el-table-column prop="endDate" label="结束"/><el-table-column prop="nextExecuteDate" label="下次执行"/><el-table-column label="状态"><template slot-scope="s"><el-tag size="mini">{{planStatus(s.row.status)}}</el-tag></template></el-table-column><el-table-column label="操作" width="300"><template slot-scope="s"><el-button type="text" @click="planDetail(s.row)">详情</el-button><el-button v-if="s.row.status===0" type="text" @click="editPlan(s.row)">编辑</el-button><el-button v-if="s.row.status===0||s.row.status===2" type="text" @click="planAct(s.row,'publish')">{{s.row.status===2?'恢复':'发布'}}</el-button><el-button v-if="s.row.status===1" type="text" @click="planAct(s.row,'pause')">暂停</el-button><el-button v-if="s.row.status===1||s.row.status===2" type="text" @click="generateTask(s.row)">生成任务</el-button><el-button v-if="s.row.status===0" type="text" class="danger" @click="deletePlan(s.row)">删除</el-button><el-button v-if="s.row.status===1||s.row.status===2" type="text" class="danger" @click="planAct(s.row,'finish')">结束</el-button></template></el-table-column></el-table></section>
- <section class="panel" v-if="planMode==='form'"><div class="sub"><el-button icon="el-icon-arrow-left" @click="planMode='list'">返回</el-button><b>{{planForm.planId?'编辑':'创建'}}巡检计划</b></div><el-steps :active="planStep" finish-status="success" simple><el-step title="基本信息"/><el-step title="选择指标"/><el-step title="选择门店"/><el-step title="确认提交"/></el-steps>
- <div v-show="planStep===0" class="step"><el-form :model="planForm" label-width="96px" class="grid"><el-form-item label="计划名称"><el-input v-model="planForm.planName"/></el-form-item><el-form-item label="计划类型"><el-select v-model="planForm.planType"><el-option label="常规巡检" :value="1"/><el-option label="专项巡检" :value="2"/><el-option label="突击检查" :value="3"/></el-select></el-form-item><el-form-item label="巡检周期"><el-select v-model="planForm.cycleType"><el-option label="每日" :value="1"/><el-option label="每周" :value="2"/><el-option label="每月" :value="3"/><el-option label="单次" :value="4"/></el-select></el-form-item><el-form-item label="巡检员ID"><el-input v-model="planForm.inspectorId"/></el-form-item><el-form-item label="巡检员"><el-input v-model="planForm.inspectorName"/></el-form-item><el-form-item label="自动采集"><el-switch v-model="planForm.autoCollectFlag" :active-value="1" :inactive-value="0"/></el-form-item><el-form-item label="日期范围" class="wide"><el-date-picker v-model="planDates" type="daterange" value-format="yyyy-MM-dd" range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期" :picker-options="pickerOptions"/></el-form-item><el-form-item label="备注" class="wide"><el-input v-model="planForm.rmk" type="textarea"/></el-form-item></el-form></div>
- <div v-show="planStep===1" class="step"><div :class="['weight',selectedWeight===100?'ok':'warn']">已选指标权重合计:{{selectedWeight}}%</div><el-table ref="indicatorTable" :data="enabledIndicators" border @selection-change="selectedIndicators=$event"><el-table-column type="selection" width="45"/><el-table-column prop="indicatorName" label="指标"/><el-table-column label="类型"><template slot-scope="s">{{typeText(s.row.indicatorType)}}</template></el-table-column><el-table-column prop="maxScore" label="满分"/><el-table-column prop="passScore" label="及格"/><el-table-column prop="weight" label="权重"/></el-table></div>
- <div v-show="planStep===2" class="step"><div class="toolbar"><el-input v-model="storeKeyword" placeholder="门店名称" clearable/><el-button @click="loadStores">搜索门店</el-button><el-button @click="manualStoreDialog=true">手动添加</el-button><span>已选 {{selectedStores.length}} 家</span></div><el-table ref="storeTable" :data="stores" border @selection-change="selectedStores=$event"><el-table-column type="selection" width="45"/><el-table-column prop="storeId" label="门店ID"/><el-table-column prop="storeName" label="门店名称"/><el-table-column prop="storeBelongto" label="校区/归属"/><el-table-column prop="city" label="城市"/></el-table></div>
- <div v-show="planStep===3" class="step"><el-descriptions title="提交确认" :column="2" border><el-descriptions-item label="计划名称">{{planForm.planName}}</el-descriptions-item><el-descriptions-item label="类型">{{planType(planForm.planType)}}</el-descriptions-item><el-descriptions-item label="周期">{{cycle(planForm.cycleType)}}</el-descriptions-item><el-descriptions-item label="日期">{{planDates.join(' 至 ')}}</el-descriptions-item><el-descriptions-item label="指标数量">{{selectedIndicators.length}}</el-descriptions-item><el-descriptions-item label="门店数量">{{selectedStores.length}}</el-descriptions-item><el-descriptions-item label="权重">{{selectedWeight}}%</el-descriptions-item><el-descriptions-item label="巡检员">{{planForm.inspectorName||planForm.inspectorId}}</el-descriptions-item></el-descriptions></div>
- <div class="actions"><el-button :disabled="planStep===0" @click="planStep--">上一步</el-button><el-button v-if="planStep<3" type="primary" @click="nextPlanStep">下一步</el-button><el-button v-else type="primary" @click="savePlanFlow">提交计划</el-button></div></section>
- <section class="panel" v-if="planMode==='detail'"><div class="sub"><el-button icon="el-icon-arrow-left" @click="planMode='list'">返回</el-button><b>计划详情</b></div><el-descriptions :column="3" border><el-descriptions-item label="计划">{{currentPlan.planName}}</el-descriptions-item><el-descriptions-item label="类型">{{planType(currentPlan.planType)}}</el-descriptions-item><el-descriptions-item label="状态">{{planStatus(currentPlan.status)}}</el-descriptions-item><el-descriptions-item label="周期">{{cycle(currentPlan.cycleType)}}</el-descriptions-item><el-descriptions-item label="开始">{{currentPlan.startDate}}</el-descriptions-item><el-descriptions-item label="结束">{{currentPlan.endDate}}</el-descriptions-item></el-descriptions><h3>关联指标</h3><el-table :data="planIndicators" border><el-table-column prop="indicatorName" label="指标"/><el-table-column prop="weight" label="权重"/><el-table-column prop="passScore" label="及格"/></el-table><h3>关联门店</h3><el-table :data="planStores" border><el-table-column prop="storeId" label="ID"/><el-table-column prop="storeName" label="门店"/><el-table-column prop="storeBelongto" label="校区"/></el-table></section>
- </el-tab-pane>
- <el-tab-pane label="巡检任务" name="task"><section class="panel"><div class="toolbar"><el-input v-model="taskQuery.planId" placeholder="计划ID" clearable/><el-input v-model="taskQuery.storeId" placeholder="门店ID" clearable/><el-select v-model="taskQuery.status" placeholder="状态" clearable><el-option label="待执行" :value="1"/><el-option label="执行中" :value="2"/><el-option label="已完成" :value="3"/><el-option label="已取消" :value="4"/></el-select><el-button type="primary" @click="loadTasks">查询</el-button><el-button @click="taskStats">统计概览</el-button></div><el-table :data="tasks" border stripe><el-table-column prop="planName" label="计划"/><el-table-column prop="storeName" label="门店"/><el-table-column prop="taskDate" label="日期"/><el-table-column prop="inspectorName" label="巡检员"/><el-table-column label="得分"><template slot-scope="s"><el-progress :percentage="percent(s.row.totalScore)" :status="s.row.passFlag===1?'success':'exception'"/></template></el-table-column><el-table-column label="状态"><template slot-scope="s">{{taskStatus(s.row.status)}}</template></el-table-column><el-table-column label="操作" width="230"><template slot-scope="s"><el-button type="text" @click="reportTask(s.row)">报告</el-button><el-button v-if="s.row.status===1" type="text" @click="startTask(s.row)">开始</el-button><el-button v-if="s.row.status===1||s.row.status===2" type="text" @click="executeTask(s.row)">执行</el-button><el-button v-if="s.row.status===1" type="text" class="danger" @click="cancelTask(s.row)">取消</el-button></template></el-table-column></el-table></section></el-tab-pane>
- <el-tab-pane label="整改管理" name="rect"><section class="panel"><div class="toolbar"><el-input v-model="rectQuery.storeId" placeholder="门店ID" clearable/><el-input v-model="rectQuery.indicatorId" placeholder="指标ID" clearable/><el-select v-model="rectQuery.status" placeholder="状态" clearable><el-option v-for="x in rectStatuses" :key="x.value" :label="x.label" :value="x.value"/></el-select><el-button type="primary" @click="loadRects">查询</el-button><el-button :disabled="!selectedRects.length" @click="batchDeadline">批量截止日期</el-button></div><el-table :data="rects" border stripe @selection-change="selectedRects=$event"><el-table-column type="selection" width="45"/><el-table-column prop="storeName" label="门店"/><el-table-column prop="indicatorName" label="指标"/><el-table-column prop="rectRound" label="轮次" width="70"/><el-table-column prop="inspectScore" label="巡检分"/><el-table-column prop="passScore" label="及格分"/><el-table-column prop="deadlineDate" label="截止"/><el-table-column label="状态"><template slot-scope="s"><el-tag size="mini" :type="rectType(s.row.status)">{{rectText(s.row.status)}}</el-tag></template></el-table-column><el-table-column label="操作" width="180"><template slot-scope="s"><el-button type="text" @click="rectDetail(s.row)">时间轴</el-button><el-button type="text" @click="urgeRect(s.row)">催办</el-button><el-button v-if="s.row.status===3" type="text" @click="openRecheck(s.row)">复检</el-button></template></el-table-column></el-table></section></el-tab-pane>
- <el-tab-pane label="考核排名" name="assess"><section class="panel"><div class="toolbar"><el-select v-model="assessQuery.periodType" placeholder="周期"><el-option label="周度" :value="1"/><el-option label="月度" :value="2"/></el-select><el-input v-model="assessQuery.periodCode" placeholder="周期编码" clearable/><el-input v-model="assessQuery.storeId" placeholder="门店ID" clearable/><el-select v-model="assessQuery.assessLevel" placeholder="等级" clearable><el-option label="优秀" :value="1"/><el-option label="良好" :value="2"/><el-option label="合格" :value="3"/><el-option label="不合格" :value="4"/></el-select><el-button type="primary" @click="loadAssess">查询</el-button><el-button @click="triggerDialog=true">手动汇总</el-button><el-button type="success" plain @click="exportAssess">导出</el-button></div><div class="stats"><div><span>门店数</span><b>{{assesses.length}}</b></div><div><span>平均综合分</span><b>{{averageScore}}</b></div><div><span>优秀</span><b>{{excellentCount}}</b></div><div><span>不合格</span><b>{{failCount}}</b></div></div><div class="charts"><div><h3>等级分布</h3><div id="levelPieChart" style="width:100%;height:220px"></div></div><div><h3>趋势对比</h3><div id="trendBarChart" style="width:100%;height:220px"></div></div></div><el-table :data="assesses" border stripe><el-table-column label="排名" width="70"><template slot-scope="s"><b :class="['rank',s.row.rankNo<=3?'top':'']">{{s.row.rankNo||s.$index+1}}</b></template></el-table-column><el-table-column prop="storeName" label="门店"/><el-table-column prop="inspectScore" label="巡检分"/><el-table-column prop="autoScore" label="自动分"/><el-table-column prop="totalScore" label="综合分"/><el-table-column label="等级"><template slot-scope="s"><el-tag size="mini" :type="levelType(s.row.assessLevel)">{{levelText(s.row.assessLevel)}}</el-tag></template></el-table-column><el-table-column prop="taskCount" label="任务"/><el-table-column prop="finishTaskCount" label="完成"/><el-table-column prop="rectCount" label="整改"/><el-table-column label="操作"><template slot-scope="s"><el-button type="text" @click="assessDetail(s.row)">详情</el-button><el-button type="text" @click="loadTrend([s.row.storeId])">趋势</el-button></template></el-table-column></el-table></section></el-tab-pane>
- </el-tabs>
- <el-dialog title="指标配置" :visible.sync="indicatorDialog" width="620px"><el-form :model="indicatorForm" label-width="92px"><el-form-item label="编码"><el-input v-model="indicatorForm.indicatorCode"/></el-form-item><el-form-item label="名称"><el-input v-model="indicatorForm.indicatorName"/></el-form-item><el-form-item label="类型"><el-radio-group v-model="indicatorForm.indicatorType"><el-radio :label="1">自动采集</el-radio><el-radio :label="2">人工巡检</el-radio></el-radio-group></el-form-item><el-form-item label="数据来源"><el-input v-model="indicatorForm.dataSource"/></el-form-item><el-form-item label="满分"><el-input-number v-model="indicatorForm.maxScore" :min="0"/></el-form-item><el-form-item label="及格"><el-input-number v-model="indicatorForm.passScore" :min="0"/></el-form-item><el-form-item label="权重(%)"><el-input-number v-model="indicatorForm.weight" :min="0" :max="100"/></el-form-item><el-form-item label="排序"><el-input-number v-model="indicatorForm.sort" :min="0"/></el-form-item><el-form-item label="评分规则"><el-input v-model="indicatorForm.scoreRule" type="textarea" :rows="4"/></el-form-item><el-form-item label="备注"><el-input v-model="indicatorForm.rmk"/></el-form-item></el-form><span slot="footer"><el-button @click="indicatorDialog=false">取消</el-button><el-button type="primary" @click="saveIndicator">保存</el-button></span></el-dialog>
- <el-dialog title="手动添加门店" :visible.sync="manualStoreDialog" width="420px"><el-form :model="manualStore" label-width="80px"><el-form-item label="门店ID"><el-input v-model="manualStore.storeId"/></el-form-item><el-form-item label="门店名称"><el-input v-model="manualStore.storeName"/></el-form-item></el-form><span slot="footer"><el-button @click="manualStoreDialog=false">取消</el-button><el-button type="primary" @click="addManualStore">添加</el-button></span></el-dialog>
- <el-dialog title="执行巡检" :visible.sync="taskDialog" width="880px"><el-descriptions v-if="currentTask.taskId" :column="3" border><el-descriptions-item label="计划">{{currentTask.planName}}</el-descriptions-item><el-descriptions-item label="门店">{{currentTask.storeName}}</el-descriptions-item><el-descriptions-item label="日期">{{currentTask.taskDate}}</el-descriptions-item></el-descriptions><h3>动态评分表单</h3><div v-for="item in taskItems" :key="item.taskItemId" :class="['score-card',item.indicatorType===1?'auto':(Number(item.score||0)<Number(item.passScore||0)?'fail':'pass')]"><b>{{item.indicatorName}}</b><el-tag size="mini">{{typeText(item.indicatorType)}}</el-tag><p v-if="item.indicatorType===1">原始值:{{item.rawValueText||item.rawValue||'-'}},得分:{{item.score}}/{{item.maxScore}},权重:{{item.weight}}%</p><div v-else><el-input-number v-model="item.score" :min="0" :max="Number(item.maxScore||100)"/><el-input v-model="item.pictureUrls" placeholder="图片URL,多个逗号分隔"/><el-input v-model="item.rmk" type="textarea" placeholder="备注"/></div></div><div class="summary"><b>加权总分:{{taskScore}}</b><el-tag :type="taskPass?'success':'danger'">{{taskPass?'及格':'不及格'}}</el-tag><span class="danger">{{failItems}}</span></div><span slot="footer"><el-button @click="taskDialog=false">取消</el-button><el-button @click="saveTaskItems">保存评分</el-button><el-button type="primary" @click="submitTask">提交巡检</el-button></span></el-dialog>
- <el-dialog title="巡检报告" :visible.sync="reportDialog" width="880px"><div class="stats"><div><span>总分</span><b>{{currentTask.totalScore||taskScore}}</b></div><div><span>满分</span><b>{{currentTask.maxScore||100}}</b></div><div><span>及格线</span><b>{{currentTask.passScore||60}}</b></div><div><span>不合格项</span><b>{{reportFails}}</b></div></div><div class="radar"><p v-for="x in taskItems" :key="x.taskItemId"><label>{{x.indicatorName}}</label><span :style="{width:percent(x.score)+'%'}">{{x.score||0}}</span></p></div><el-table :data="taskItems" border><el-table-column prop="indicatorName" label="指标"/><el-table-column prop="score" label="得分"/><el-table-column prop="passScore" label="及格"/><el-table-column prop="weightedScore" label="加权"/><el-table-column prop="pictureUrls" label="照片"/><el-table-column prop="rmk" label="备注"/></el-table></el-dialog>
- <el-dialog title="整改时间轴" :visible.sync="rectDialog" width="720px"><el-timeline><el-timeline-item color="#e6a23c" :timestamp="currentRect.createDate||currentRect.deadlineDate"><h4>第 {{currentRect.rectRound||1}} 轮整改下发</h4><p>{{currentRect.noticeContent||'巡检不合格,需整改'}}</p><p>巡检分:{{currentRect.inspectScore}},及格:{{currentRect.passScore}},截止:{{currentRect.deadlineDate}}</p></el-timeline-item><el-timeline-item v-if="currentRect.feedbackContent" color="#409eff" :timestamp="currentRect.feedbackDate"><h4>门店反馈</h4><p>{{currentRect.feedbackContent}}</p><p>{{currentRect.feedbackPictures}}</p></el-timeline-item><el-timeline-item v-if="currentRect.recheckResult!==null&¤tRect.recheckResult!==undefined" :color="currentRect.recheckResult===1?'#67c23a':'#f56c6c'" :timestamp="currentRect.recheckDate"><h4>巡检复检</h4><p>得分:{{currentRect.recheckScore}},结果:{{currentRect.recheckResult===1?'通过':'不通过'}}</p><p>{{currentRect.recheckRmk}}</p></el-timeline-item></el-timeline><span slot="footer"><el-button @click="rectDialog=false">关闭</el-button><el-button v-if="currentRect.status===3" type="primary" @click="openRecheck(currentRect)">复检</el-button></span></el-dialog>
- <el-dialog title="复检操作" :visible.sync="recheckDialog" width="520px"><el-form :model="recheckForm" label-width="90px"><el-form-item label="复检得分"><el-input-number v-model="recheckForm.recheckScore" :min="0" :max="100"/></el-form-item><el-form-item label="结果"><el-radio-group v-model="recheckForm.recheckResult"><el-radio :label="1">通过</el-radio><el-radio :label="0">不通过</el-radio></el-radio-group></el-form-item><el-form-item label="照片"><el-input v-model="recheckForm.recheckPictures"/></el-form-item><el-form-item label="备注"><el-input v-model="recheckForm.recheckRmk" type="textarea"/></el-form-item></el-form><span slot="footer"><el-button @click="recheckDialog=false">取消</el-button><el-button type="primary" @click="submitRecheck">提交</el-button></span></el-dialog>
- <el-dialog title="手动汇总" :visible.sync="triggerDialog" width="520px"><el-form :model="triggerForm" label-width="90px"><el-form-item label="周期"><el-select v-model="triggerForm.periodType"><el-option label="周度" :value="1"/><el-option label="月度" :value="2"/></el-select></el-form-item><el-form-item label="编码"><el-input v-model="triggerForm.periodCode"/></el-form-item><el-form-item label="日期"><el-date-picker v-model="triggerRange" type="daterange" value-format="yyyy-MM-dd"/></el-form-item></el-form><span slot="footer"><el-button @click="triggerDialog=false">取消</el-button><el-button type="primary" @click="triggerAssess">汇总</el-button></span></el-dialog>
- <el-dialog title="考核详情" :visible.sync="assessDialog" width="820px"><el-descriptions :column="3" border><el-descriptions-item label="门店">{{currentAssess.storeName}}</el-descriptions-item><el-descriptions-item label="周期">{{currentAssess.periodCode}}</el-descriptions-item><el-descriptions-item label="综合分">{{currentAssess.totalScore}}</el-descriptions-item><el-descriptions-item label="巡检分">{{currentAssess.inspectScore}}</el-descriptions-item><el-descriptions-item label="自动分">{{currentAssess.autoScore}}</el-descriptions-item><el-descriptions-item label="等级">{{levelText(currentAssess.assessLevel)}}</el-descriptions-item></el-descriptions><el-table :data="assessDetails" border><el-table-column prop="planName" label="计划/指标"/><el-table-column prop="taskDate" label="日期"/><el-table-column prop="totalScore" label="得分"/><el-table-column prop="status" label="状态"/></el-table></el-dialog>
- </div>
- </template>
- <script>
- import querystring from 'querystring'
- export default {name:'InspectManage',data(){return{tab:'indicator',planMode:'list',planStep:0,indicators:[],enabledIndicators:[],plans:[],stores:[],tasks:[],taskItems:[],rects:[],assesses:[],assessDetails:[],trend:[],indicatorQuery:{},planQuery:{},taskQuery:{},rectQuery:{},assessQuery:{periodType:2},indicatorDialog:false,indicatorForm:{},planForm:{planType:1,cycleType:2,autoCollectFlag:1,status:0},planDates:[],pickerOptions:{disabledDate(time){const today=new Date();today.setHours(0,0,0,0);return time.getTime()<today.getTime()}},selectedIndicators:[],selectedStores:[],currentPlan:{},planIndicators:[],planStores:[],storeKeyword:'',manualStoreDialog:false,manualStore:{},currentTask:{},taskDialog:false,reportDialog:false,selectedRects:[],currentRect:{},rectDialog:false,recheckDialog:false,recheckForm:{},triggerDialog:false,triggerForm:{periodType:2},triggerRange:[],assessDialog:false,currentAssess:{},rectStatuses:[{value:1,label:'待整改'},{value:2,label:'整改中'},{value:3,label:'待复检'},{value:4,label:'复检通过'},{value:5,label:'复检不通过'},{value:6,label:'已超期'},{value:7,label:'最终不通过'}]}},computed:{selectedWeight(){return this.selectedIndicators.reduce((s,x)=>s+Number(x.weight||0),0)},taskScore(){return this.taskItems.reduce((s,x)=>s+Number(x.score||0)*Number(x.weight||0)/100,0).toFixed(2)},taskPass(){return Number(this.taskScore)>=this.taskItems.reduce((s,x)=>s+Number(x.passScore||0)*Number(x.weight||0)/100,0)},failItems(){return this.taskItems.filter(x=>Number(x.score||0)<Number(x.passScore||0)).map(x=>x.indicatorName).join('、')},reportFails(){return this.taskItems.filter(x=>Number(x.score||0)<Number(x.passScore||0)).length},averageScore(){return this.assesses.length?(this.assesses.reduce((s,x)=>s+Number(x.totalScore||0),0)/this.assesses.length).toFixed(2):'0.00'},excellentCount(){return this.assesses.filter(x=>x.assessLevel===1).length},failCount(){return this.assesses.filter(x=>x.assessLevel===4).length},levelBars(){let m=[{id:1,name:'优秀',color:'#67c23a'},{id:2,name:'良好',color:'#409eff'},{id:3,name:'合格',color:'#e6a23c'},{id:4,name:'不合格',color:'#f56c6c'}];return m.map(x=>Object.assign({},x,{value:this.assesses.filter(a=>a.assessLevel===x.id).length}))},trendBars(){let list=this.trend.length?this.trend:this.assesses.slice(0,6).map(x=>({label:x.storeName,value:x.totalScore}));return list.map((x,i)=>({label:x.periodCode||x.storeName||('T'+i),value:Number(x.totalScore||x.value||0)}))}},created(){this.initAuth();this.load()},methods:{initAuth(){let token=localStorage.getItem('Authorization');if(token){this.$axios.defaults.headers.common['Authorization']=token}},saveToken(token){localStorage.setItem('Authorization',token);this.$axios.defaults.headers.common['Authorization']=token},post(u,d){let p={};if(d){Object.keys(d).forEach(k=>{let v=d[k];if(v!==null&&v!==undefined&&v!=='')p[k]=v})}return this.$axios.post(u,querystring.stringify(p))},download(u,d,name){this.$axios.post(u,querystring.stringify(d||{}),{responseType:'blob'}).then(r=>{let blob=new Blob([r.data],{type:'application/vnd.ms-excel'});let a=document.createElement('a');a.href=URL.createObjectURL(blob);a.download=name;a.click();this.$message.success('导出完成')}).catch(()=>this.$message.error('导出失败'))},rows(r){return r.data&&(r.data.list||(r.data.obj&&r.data.obj.list))||[]},load(){({indicator:this.loadIndicators,plan:this.loadPlans,task:this.loadTasks,rect:this.loadRects,assess:this.loadAssess})[this.tab]()},loadIndicators(){this.post('/inspectManage/indicator/page',Object.assign({pageSize:50,currentPage:1},this.indicatorQuery)).then(r=>{this.indicators=this.rows(r);this.enabledIndicators=this.indicators.filter(x=>x.status===1)})},openIndicator(r){this.indicatorForm=Object.assign({indicatorType:2,maxScore:100,passScore:60,weight:0,sort:0,status:1},r||{});this.indicatorDialog=true},saveIndicator(){this.post(this.indicatorForm.indicatorId?'/inspectManage/indicator/update':'/inspectManage/indicator/save',this.indicatorForm).then(r=>{this.indicatorDialog=false;this.loadIndicators();let msg=(r.data&&r.data.rtnBean&&r.data.rtnBean.rtnMsg)||'保存完成';this.$message.success(msg)})},changeIndicator(r){this.post('/inspectManage/indicator/changeStatus',{id:r.indicatorId,status:r.status})},removeIndicator(r){this.$confirm('确认删除该指标?','提示').then(()=>this.post('/inspectManage/indicator/delete',{id:r.indicatorId}).then(()=>this.loadIndicators()))},checkWeight(){this.post('/inspectManage/indicator/checkWeight').then(r=>this.$message.info('当前启用指标权重合计:'+(r.data.obj||0)+'%'))},loadPlans(){this.post('/inspectManage/plan/page',Object.assign({pageSize:50,currentPage:1},this.planQuery)).then(r=>{this.plans=this.rows(r)})},openPlan(){this.planMode='form';this.planStep=0;this.planForm={planType:1,cycleType:2,autoCollectFlag:1,status:0};const d=new Date();this.planDates=[d.getFullYear()+'-'+String(d.getMonth()+1).padStart(2,'0')+'-'+String(d.getDate()).padStart(2,'0'),''];this.selectedIndicators=[];this.selectedStores=[];this.loadIndicators();this.loadStores()},editPlan(r){this.openPlan();this.planForm=Object.assign({},r);this.planDates=[r.startDate,r.endDate].filter(Boolean)},nextPlanStep(){if(this.planStep===1&&!this.selectedIndicators.length)return this.$message.warning('请至少选择一个指标');if(this.planStep===2&&!this.selectedStores.length)return this.$message.warning('请至少选择一家门店');this.planStep++},loadStores(){this.post('/storeManage/getStoreList',{storeName:this.storeKeyword}).then(r=>{this.stores=this.rows(r)})},addManualStore(){if(!this.manualStore.storeId||!this.manualStore.storeName)return this.$message.warning('请输入门店ID和名称');this.stores.push(Object.assign({},this.manualStore));this.selectedStores.push(Object.assign({},this.manualStore));this.manualStore={};this.manualStoreDialog=false},savePlanFlow(){if(this.planDates.length===2){this.planForm.startDate=this.planDates[0];this.planForm.endDate=this.planDates[1];this.planForm.nextExecuteDate=this.planDates[0]}this.post(this.planForm.planId?'/inspectManage/plan/update':'/inspectManage/plan/save',this.planForm).then(r=>{let id=this.planForm.planId||(r.data&&r.data.obj);let jobs=[];this.selectedIndicators.forEach((x,i)=>jobs.push(this.post('/inspectManage/planIndicator/save',{planId:id,indicatorId:x.indicatorId,indicatorName:x.indicatorName,indicatorType:x.indicatorType,maxScore:x.maxScore,passScore:x.passScore,weight:x.weight,scoreRule:x.scoreRule,sort:i+1,status:1})));this.selectedStores.forEach(x=>jobs.push(this.post('/inspectManage/planStore/save',{planId:id,storeId:x.storeId,storeName:x.storeName,storeBelongto:x.storeBelongto,province:x.province,city:x.city,status:1})));Promise.all(jobs).then(()=>{this.planMode='list';this.loadPlans();this.$message.success('计划保存完成')})})},planDetail(r){this.currentPlan=r;this.planMode='detail';this.post('/inspectManage/plan/indicators',{planId:r.planId}).then(x=>this.planIndicators=this.rows(x));this.post('/inspectManage/plan/stores',{planId:r.planId}).then(x=>this.planStores=this.rows(x))},planAct(r,a){this.post('/inspectManage/plan/'+a,{id:r.planId}).then(()=>{this.loadPlans();this.$message.success('操作完成')})},deletePlan(r){this.$confirm('确认删除该草稿计划?','提示').then(()=>this.post('/inspectManage/plan/delete',{id:r.planId}).then(()=>this.loadPlans()))},generateTask(r){this.$prompt('请输入任务日期 yyyy-MM-dd','生成任务').then(({value})=>this.post('/inspectManage/task/generate',{planId:r.planId,taskDate:value}).then(x=>this.$message.success('生成任务数:'+(x.data.obj||0))))},loadTasks(){this.post('/inspectManage/task/page',Object.assign({pageSize:50,currentPage:1},this.taskQuery)).then(r=>this.tasks=this.rows(r))},taskStats(){this.post('/inspectManage/task/statistics',{planId:this.taskQuery.planId}).then(r=>this.$alert(JSON.stringify(this.rows(r),null,2),'任务统计'))},startTask(r){this.$prompt('请输入巡检员ID','开始执行').then(({value})=>this.post('/inspectManage/task/start',{id:r.taskId,inspectorId:value}).then(()=>this.loadTasks()))},cancelTask(r){this.$confirm('确认取消?','提示').then(()=>this.post('/inspectManage/task/cancel',{id:r.taskId}).then(()=>this.loadTasks()))},executeTask(r){this.currentTask=r;this.taskDialog=true;this.loadTaskItems(r.taskId)},reportTask(r){this.currentTask=r;this.reportDialog=true;this.loadTaskItems(r.taskId)},loadTaskItems(id){this.post('/inspectManage/task/items',{taskId:id}).then(r=>this.taskItems=this.rows(r))},saveTaskItems(){Promise.all(this.taskItems.map(x=>this.post(x.taskItemId?'/inspectManage/taskItem/update':'/inspectManage/taskItem/save',x))).then(()=>this.$message.success('评分已保存'))},submitTask(){this.saveTaskItems();this.$prompt('请输入巡检员ID','提交巡检').then(({value})=>this.post('/inspectManage/task/submit',{id:this.currentTask.taskId,inspectorId:value}).then(()=>{this.taskDialog=false;this.loadTasks()}))},loadRects(){this.post('/inspectManage/rectification/page',Object.assign({pageSize:50,currentPage:1},this.rectQuery)).then(r=>this.rects=this.rows(r))},rectDetail(r){this.currentRect=r;this.rectDialog=true},urgeRect(r){this.post('/inspectManage/rectification/urge',{id:r.rectId}).then(()=>this.$message.success('催办完成'))},batchDeadline(){this.$prompt('请输入截止日期 yyyy-MM-dd HH:mm:ss','批量截止日期').then(({value})=>this.post('/inspectManage/rectification/batchUpdateDeadline',{rectIds:this.selectedRects.map(x=>x.rectId),deadlineDate:value}).then(()=>this.loadRects()))},openRecheck(r){this.currentRect=r;this.recheckForm={id:r.rectId,recheckScore:r.passScore||60,recheckResult:1};this.recheckDialog=true},submitRecheck(){this.post('/inspectManage/rectification/recheck',this.recheckForm).then(()=>{this.recheckDialog=false;this.rectDialog=false;this.loadRects()})},loadAssess(){this.post('/inspectManage/assess/rankPage',Object.assign({pageSize:50,currentPage:1},this.assessQuery)).then(r=>{this.assesses=this.rows(r);this.loadTrend(this.assesses.slice(0,3).map(x=>x.storeId).filter(Boolean));this.$nextTick(()=>this.renderCharts())})},loadTrend(ids){if(!ids||!ids.length)return;this.post('/inspectManage/assess/trend',{storeIds:ids,periodType:this.assessQuery.periodType}).then(r=>{this.trend=this.rows(r);this.$nextTick(()=>this.renderCharts())}).catch(()=>{})},renderCharts(){this.loadEcharts(()=>{let pie=document.getElementById('levelPieChart'),bar=document.getElementById('trendBarChart');if(pie){let e=window.echarts.init(pie);let levels=[{name:'优秀',count:this.excellentCount},{name:'良好',count:this.assesses.filter(x=>x.assessLevel===2).length},{name:'合格',count:this.assesses.filter(x=>x.assessLevel===3).length},{name:'不合格',count:this.failCount}];e.setOption({tooltip:{trigger:'item'},color:['#67c23a','#409eff','#e6a23c','#f56c6c'],series:[{type:'pie',radius:['40%','70%'],data:levels.map(x=>({name:x.name,value:x.count})),label:{formatter:'{b}: {c}'},emphasis:{itemStyle:{shadowBlur:10,shadowOffsetX:0,shadowColor:'rgba(0,0,0,0.5)'}}}]})}if(bar){let e2=window.echarts.init(bar);let data=this.trend.length?this.trend:this.assesses.slice(0,6).map(x=>({storeName:x.storeName,periodCode:x.periodCode,totalScore:x.totalScore}));e2.setOption({tooltip:{trigger:'axis'},color:['#409eff','#67c23a'],xAxis:{type:'category',data:data.map(x=>x.periodCode||x.storeName||'')},yAxis:{type:'value',name:'综合得分'},series:[{type:'bar',data:data.map(x=>Number(x.totalScore||0)),barMaxWidth:40}]})}})},loadEcharts(cb){if(window.echarts){cb();return}let s=document.createElement('script');s.src='https://cdn.jsdelivr.net/npm/echarts@5.5.0/dist/echarts.min.js';s.onload=cb;document.head.appendChild(s)},triggerAssess(){let d=Object.assign({},this.triggerForm,{periodStartDate:this.triggerRange[0],periodEndDate:this.triggerRange[1]});this.post('/inspectManage/assess/trigger',d).then(()=>{this.triggerDialog=false;this.loadAssess()})},exportAssess(){this.download('/inspectManage/assess/exportExcel',this.assessQuery,'考核排名_'+this.assessQuery.periodCode+'.xlsx')},assessDetail(r){this.currentAssess=r;this.assessDialog=true;this.post('/inspectManage/assess/detail',{storeId:r.storeId,periodType:this.assessQuery.periodType,periodCode:this.assessQuery.periodCode}).then(x=>this.assessDetails=this.rows(x))},percent(v){return Math.max(0,Math.min(100,Number(v||0)))},typeText(v){return v===1?'自动采集':'人工巡检'},planType(v){return{1:'常规巡检',2:'专项巡检',3:'突击检查'}[v]||'-'},cycle(v){return{1:'每日',2:'每周',3:'每月',4:'单次'}[v]||'-'},planStatus(v){return{0:'草稿',1:'已发布',2:'已暂停',3:'已结束'}[v]||'-'},taskStatus(v){return{1:'待执行',2:'执行中',3:'已完成',4:'已取消'}[v]||'-'},rectText(v){return(this.rectStatuses.find(x=>x.value===v)||{}).label||'-'},rectType(v){return{1:'warning',2:'primary',3:'',4:'success',5:'danger',6:'info',7:'danger'}[v]||'info'},levelText(v){return{1:'优秀',2:'良好',3:'合格',4:'不合格'}[v]||'-'},levelType(v){return{1:'success',2:'primary',3:'warning',4:'danger'}[v]||'info'}}}
- </script>
- <style scoped>
- .inspect-page{padding:20px;background:#f5f7fa;min-height:100%;color:#303133}.head{display:flex;justify-content:space-between;align-items:center;margin-bottom:18px}.head h2{margin:0;font-size:22px}.head p{margin:7px 0 0;color:#909399}.panel{background:#fff;border:1px solid #ebeef5;border-radius:4px;padding:18px}.toolbar{display:flex;gap:10px;align-items:center;flex-wrap:wrap;margin-bottom:16px}.toolbar .el-input,.toolbar .el-select{width:170px}.grow{flex:1}.danger{color:#f56c6c}.sub{display:flex;align-items:center;gap:14px;margin-bottom:16px}.step{padding:22px 0}.grid{display:grid;grid-template-columns:1fr 1fr;column-gap:18px}.wide{grid-column:1/3}.actions{text-align:right;border-top:1px solid #ebeef5;padding-top:16px}.weight{padding:10px 14px;border-radius:4px;margin-bottom:14px}.weight.ok{background:#f0f9eb;color:#67c23a}.weight.warn{background:#fdf6ec;color:#e6a23c}.stats{display:grid;grid-template-columns:repeat(4,1fr);gap:14px;margin-bottom:18px}.stats>div{border:1px solid #ebeef5;border-radius:4px;padding:14px 18px}.stats span{display:block;color:#909399;margin-bottom:6px}.stats b{font-size:24px;color:#409eff}.charts{display:grid;grid-template-columns:1fr 1fr;gap:14px;margin-bottom:18px}.charts>div{border:1px solid #ebeef5;border-radius:4px;padding:14px}.charts p,.radar p{display:flex;align-items:center;gap:10px}.charts label,.radar label{width:90px;color:#606266}.charts span,.radar span{display:inline-block;color:#fff;border-radius:3px;padding:3px 8px;min-width:28px}.rank{display:inline-flex;width:24px;height:24px;border-radius:50%;align-items:center;justify-content:center;background:#f0f2f5}.rank.top{background:#f56c6c;color:#fff}.score-card{border:1px solid #ebeef5;border-left:4px solid #67c23a;border-radius:4px;padding:12px;margin-bottom:10px}.score-card.auto{background:#fafafa;border-left-color:#409eff}.score-card.fail{border-left-color:#f56c6c}.score-card .el-input{margin:8px 0}.summary{display:flex;gap:14px;align-items:center;background:#f5f7fa;padding:12px;border-radius:4px}.radar span{background:#409eff}@media(max-width:900px){.grid,.stats,.charts{grid-template-columns:1fr}.wide{grid-column:auto}}
- </style>
|