2 Revize 9ba7d75958 ... f8e0a7e184

Autor SHA1 Zpráva Datum
  admin f8e0a7e184 feat: 更新前端配置和axios及登录页面 před 3 týdny
  admin 3a4fabe9e9 门店巡检与考核管理模块前端 před 3 týdny

+ 29 - 0
.claude/settings.json

@@ -0,0 +1,29 @@
+{
+  "permissions": {
+    "allow": [
+      "Bash('/c/Program Files/MySQL/MySQL Workbench 8.0 CE/mysql' -h192.168.200.4 -P3306 -uroot -pAbcd@1234 -N -e ' *)",
+      "Bash(\"/c/Program Files/MySQL/MySQL Workbench 8.0 CE/mysql\" -h192.168.200.4 -P3306 -uroot -p'Abcd@1234' \"eduepos-plat\")",
+      "Bash(\"/c/Program Files/MySQL/MySQL Workbench 8.0 CE/mysql\" -h192.168.200.4 -P3306 -uroot -p'Abcd@1234' -N -e \"SHOW TABLES FROM \\\\`eduepos-plat\\\\` LIKE '%char%'; SHOW TABLES FROM \\\\`eduepos-plat\\\\` LIKE '%role%';\")",
+      "Bash('/c/Program Files/MySQL/MySQL Workbench 8.0 CE/mysql' -h192.168.200.4 -P3306 -uroot -pAbcd@1234 eduepos -e ' *)",
+      "Bash('/c/Program Files/MySQL/MySQL Workbench 8.0 CE/mysql' --default-character-set=utf8mb4 -h192.168.200.4 -P3306 -uroot -pAbcd@1234 -N -e ' *)",
+      "Bash(\"/c/Program Files/MySQL/MySQL Workbench 8.0 CE/mysql\" --default-character-set=utf8mb4 -h192.168.200.4 -P3306 -uroot -p'Abcd@1234' \"eduepos\" -e \"DELETE FROM bu_inspect_indicator;\")",
+      "Bash(\"/c/Program Files/MySQL/MySQL Workbench 8.0 CE/mysql\" --default-character-set=utf8mb4 -h192.168.200.4 -P3306 -uroot -p'Abcd@1234' \"eduepos\")",
+      "Bash(xargs grep -n \"getSerchCondition\")",
+      "Bash(xargs cat)",
+      "Bash(grep -n \"searchBySql\" \"C:/Users/admin/Downloads/eduepos_server-master/eduepos_server/eduepos-group/common/src/main/java/com/sapit/common/framwork/BaseDao.java\" | head; echo \"---impl---\"; sed -n '/public List<?> searchBySql/,/^\t}/p' \"C:/Users/admin/Downloads/eduepos_server-master/eduepos_server/eduepos-group/common/src/main/java/com/sapit/common/framwork/BaseDao.java\" | head -30)",
+      "Bash(find \"C:/Users/admin/Downloads/eduepos_server-master/eduepos_server\" -maxdepth 4 -name \"*.log\" -newermt \"2026-08-21\" 2>/dev/null | head; echo \"---common log dirs---\"; ls -la \"C:/Users/admin/Downloads/eduepos_server-master/eduepos_server/\" 2>/dev/null | grep -iE \"log|nohup\"; find \"C:/Users/admin/Downloads\" -maxdepth 3 -name \"nohup.out\" -newermt \"2026-08-20\" 2>/dev/null | head)",
+      "Read(//c/Users/admin/Downloads/**)",
+      "Bash(grep -n \"searchByHql\" \"C:/Users/admin/Downloads/eduepos_server-master/eduepos_server/eduepos-group/common/src/main/java/com/sapit/common/framwork/BaseDao.java\" | head; echo \"---impl---\"; sed -n '/public List<T> searchByHql\\(PageControlData page/,/^\t}/p' \"C:/Users/admin/Downloads/eduepos_server-master/eduepos_server/eduepos-group/common/src/main/java/com/sapit/common/framwork/BaseDao.java\")",
+      "Bash(curl -s -o /dev/null -w \"HTTP %{http_code}\\\\n\" -X POST \"http://localhost:8081/a1/inspectManage/indicator/page\" --data \"pageSize=10&currentPage=1\")",
+      "Bash(curl -s -o /dev/null -w \"HTTP %{http_code}\\\\n\" -X POST \"http://localhost:8081/a1/inspectManage/task/page\" --data \"pageSize=10&currentPage=1\")",
+      "Bash(curl -s -o /dev/null -w \"HTTP %{http_code}\\\\n\" -X POST \"http://localhost:8081/a1/inspectManage/task/statistics\")",
+      "Bash(curl -s -o /dev/null -w \"HTTP %{http_code}\\\\n\" -X POST \"http://localhost:8081/a1/inspectManage/rectification/page\" --data \"pageSize=10&currentPage=1\")",
+      "Bash(grep -n 'post\\(this.$axios, '\\\\''/inspectManage/plan/page'\\\\''' src/components/page/admin/inspect/plan/Index.vue)",
+      "Bash(git add *)",
+      "Bash(git commit -m '门店巡检与考核管理模块前端 *)"
+    ],
+    "additionalDirectories": [
+      "C:\\Users\\admin\\Downloads\\eduepos_server-master\\eduepos_server\\eduepos-group\\common\\src\\main\\java\\com\\sapit\\common\\framwork"
+    ]
+  }
+}

+ 54 - 2
.claude/settings.local.json

@@ -35,10 +35,62 @@
       "Bash(curl -s -o /dev/null -w \"%{http_code}\" -X POST \"http://localhost:8081/a1/orderYear/homeDataInfo\")",
       "Bash(git add *)",
       "Bash(git commit *)",
-      "Bash(git push *)"
+      "Bash(git push *)",
+      "Read(//c/Users/admin/Downloads/eduepos_server-master/eduepos_server/eduepos-group/**)",
+      "Read(//c/Users/admin/Downloads/eduepos_server-master/eduepos_server/eduepos-group/client-app/src/main/java/com/sapit/client/app/manage/inspect/controller/**)",
+      "Read(//c/Users/admin/Downloads/eduepos_server-master/eduepos_server/eduepos-group/common/src/main/java/com/sapit/common/entity/inspect/**)",
+      "Read(//c/Users/admin/Downloads/eduepos_server-master/eduepos_server/eduepos-group/client-app/src/main/java/com/sapit/client/app/manage/inspect/service/**)",
+      "Bash(grep -rln 'Vue.prototype.$axios\\\\|baseURL' src/)",
+      "Bash(grep -rn \"getStoreList\" \"C:/Users/admin/Downloads/eduepos_server-master/eduepos_server/eduepos-group/client-app/src/main/java/com/sapit/client/app/manage\" --include=*.java | grep -i \"controller\\\\|@RequestMapping\\\\|@PostMapping\" | head; echo \"---\"; grep -rln \"getStoreList\" \"C:/Users/admin/Downloads/eduepos_server-master/eduepos_server/eduepos-group/client-app/src/main/java\" | head)",
+      "Read(//c/Users/admin/Downloads/eduepos_server-master/eduepos_server/eduepos-group/client-app/src/main/java/com/sapit/client/app/manage/**)",
+      "Read(//c/Users/admin/Downloads/eduepos_server-master/eduepos_server/eduepos-group/client-app/src/main/java/**)",
+      "Read(//c/Users/admin/Downloads/eduepos_server-master/eduepos_server/eduepos-group/client-app/src/main/java/com/sapit/client/app/manage/controller/store/**)",
+      "Bash(grep -rn \"private\" \"C:/Users/admin/Downloads/eduepos_server-master/eduepos_server/eduepos-group/common/src/main/java/com/sapit/common/search/store/StoreSearchBean.java\" 2>/dev/null | head -40; echo \"---BuStore fields---\"; grep -n \"private\" \"C:/Users/admin/Downloads/eduepos_server-master/eduepos_server/eduepos-group/common/src/main/java/com/sapit/common/entity/store/BuStore.java\" 2>/dev/null | head -40)",
+      "Read(//c/Users/admin/Downloads/eduepos_server-master/eduepos_server/eduepos-group/common/src/main/java/com/sapit/common/search/store/**)",
+      "Read(//c/Users/admin/Downloads/eduepos_server-master/eduepos_server/eduepos-group/common/src/main/java/com/sapit/common/entity/store/**)",
+      "Bash(xargs grep -ln \"MessageConverter\\\\|ObjectMapper\\\\|FastJson\")",
+      "Read(//c/Users/admin/Downloads/eduepos_server-master/eduepos_server/eduepos-group/common/src/main/java/com/sapit/common/framwork/**)",
+      "Bash(grep -rn \"jackson\\\\|date-format\\\\|time-zone\" \"C:/Users/admin/Downloads/eduepos_server-master/eduepos_server/eduepos-group\" --include=*.yml --include=*.properties -i 2>/dev/null | head; echo \"---application.yml locations---\"; find \"C:/Users/admin/Downloads/eduepos_server-master/eduepos_server/eduepos-group\" -name \"application*.yml\" | head)",
+      "Read(//c/Users/admin/Downloads/eduepos_server-master/eduepos_server/eduepos-group/frontend-inspect-components/**)",
+      "Read(//c/Users/admin/Documents/xwechat_files/wxid_8osj9yuurtq522_7dc4/msg/file/2026-08/**)",
+      "Bash(cd \"c:/Users/admin/Downloads/eduepos_ui_manage-master/eduepos_ui_manage\" && grep -n \"exportData\" src/components/page/admin/dataStatistics/GoodsStatistics.vue | head -3; echo \"---\"; sed -n '/exportData\\(\\)/,/^        },/p' src/components/page/admin/dataStatistics/GoodsStatistics.vue | head -30)",
+      "Read(//c/Users/admin/Downloads/eduepos_server-master/eduepos_server/eduepos-group/client-app/src/main/java/com/sapit/client/app/manage/controller/**)",
+      "Bash(cd \"c:/Users/admin/Downloads/eduepos_ui_manage-master/eduepos_ui_manage\" && grep -n \"Url:\" src/components/page/admin/delivery/DeliveryerManage1.vue | head; echo \"---backend deliveryer controller---\"; grep -rn \"@RequestMapping\\\\|@PostMapping\" \"C:/Users/admin/Downloads/eduepos_server-master/eduepos_server/eduepos-group/client-app/src/main/java/com/sapit/client/app/manage/controller/rider/DeliveryerManageController.java\" 2>/dev/null | head -20)",
+      "Read(//c/Users/admin/Downloads/eduepos_server-master/eduepos_server/eduepos-group/client-app/src/main/java/com/sapit/client/app/manage/controller/rider/**)",
+      "Bash(grep -rn \"getUserPage\" \"C:/Users/admin/Downloads/eduepos_server-master/eduepos_server/eduepos-group/client-app/src/main/java/com/sapit/client/app\" --include=*.java | head -5; echo \"---\"; grep -rn \"@RequestMapping\\\\|getUserPage\\\\|getCharactorList\" \"C:/Users/admin/Downloads/eduepos_server-master/eduepos_server/eduepos-group/client-app/src/main/java/com/sapit/client/app/api/controller/UserManageController.java\" 2>/dev/null | head -20)",
+      "Read(//c/Users/admin/Downloads/eduepos_server-master/eduepos_server/eduepos-group/client-app/src/main/java/com/sapit/client/app/**)",
+      "Read(//c/Users/admin/Downloads/eduepos_server-master/eduepos_server/eduepos-group/client-app/src/main/java/com/sapit/client/app/api/controller/**)",
+      "Bash(grep -rln \"getUserPage\" \"C:/Users/admin/Downloads/eduepos_server-master/eduepos_server/eduepos-group\" --include=*.java | head)",
+      "Bash(grep -n \"getUserPage\\\\|@RequestMapping\\\\|@PostMapping\\\\|getCharactorList\" \"C:/Users/admin/Downloads/eduepos_server-master/eduepos_server/eduepos-group/client-plat/src/main/java/com/sapit/client/plat/controller/UserManageController.java\" | head -20; echo \"---User entity fields---\"; grep -n \"private\" \"C:/Users/admin/Downloads/eduepos_server-master/eduepos_server/eduepos-group/common/src/main/java/com/sapit/common/entity/user/User.java\" 2>/dev/null | head -20)",
+      "Read(//c/Users/admin/Downloads/eduepos_server-master/eduepos_server/eduepos-group/client-plat/src/main/java/com/sapit/client/plat/controller/**)",
+      "Read(//c/Users/admin/Downloads/eduepos_server-master/eduepos_server/eduepos-group/common/src/main/java/com/sapit/common/entity/user/**)",
+      "Bash(find \"C:/Users/admin/Downloads/eduepos_server-master/eduepos_server/eduepos-group/common\" -name \"SysUserSearchBean.java\" -o -name \"SysUser.java\" | head; echo \"---\"; grep -n \"private\" \"C:/Users/admin/Downloads/eduepos_server-master/eduepos_server/eduepos-group/common/src/main/java/com/sapit/common/entity/user/SysUser.java\" 2>/dev/null | head -20; echo \"---searchbean---\"; grep -n \"private\" \"C:/Users/admin/Downloads/eduepos_server-master/eduepos_server/eduepos-group/common/src/main/java/com/sapit/common/search/user/SysUserSearchBean.java\" 2>/dev/null | head -20)",
+      "Read(//c/Users/admin/Downloads/eduepos_server-master/eduepos_server/eduepos-group/common/**)",
+      "Read(//c/Users/admin/Downloads/eduepos_server-master/eduepos_server/eduepos-group/common/src/main/java/com/sapit/common/search/user/**)",
+      "Bash(grep -n \"private\" \"C:/Users/admin/Downloads/eduepos_server-master/eduepos_server/eduepos-group/common/src/main/java/com/sapit/common/entity/system/SysUser.java\" | head -30; echo \"---searchbean---\"; grep -n \"private\" \"C:/Users/admin/Downloads/eduepos_server-master/eduepos_server/eduepos-group/common/src/main/java/com/sapit/common/search/system/SysUserSearchBean.java\" | head -20)",
+      "Read(//c/Users/admin/Downloads/eduepos_server-master/eduepos_server/eduepos-group/common/src/main/java/com/sapit/common/entity/system/**)",
+      "Read(//c/Users/admin/Downloads/eduepos_server-master/eduepos_server/eduepos-group/common/src/main/java/com/sapit/common/search/system/**)",
+      "Read(//c/Users/admin/Downloads/eduepos_server-master/eduepos_server/eduepos-group/client-app/src/main/java/com/sapit/client/app/manage/inspect/persistence/**)",
+      "Read(//c/Users/admin/Downloads/eduepos_server-master/eduepos_server/eduepos-group/client-app/src/main/java/com/sapit/client/app/persistence/**)",
+      "Bash(xargs grep -n \"public boolean save\\\\|public.*save\\\\|getId\\\\|get\\(\\\\\"\")",
+      "Bash(xargs sed -n '150,190p')",
+      "Bash(find \"C:/Users/admin/Downloads/eduepos_server-master/eduepos_server/eduepos-group/common/src/main/resources/mapper/inspect\" -type f | head; echo \"---plan mapper---\"; cat \"C:/Users/admin/Downloads/eduepos_server-master/eduepos_server/eduepos-group/common/src/main/resources/mapper/inspect/BuInspectPlan.xml\" 2>/dev/null | head -80)",
+      "Read(//c/Users/admin/Downloads/eduepos_server-master/eduepos_server/eduepos-group/common/src/main/resources/mapper/inspect/**)",
+      "Bash(grep -rln \"CustomDateEditor\\\\|TimestampEditor\\\\|PropertyEditor\\\\|@InitBinder\\\\|ConverterFactory\\\\|WebDataBinder\" \"C:/Users/admin/Downloads/eduepos_server-master/eduepos_server/eduepos-group\" --include=*.java | head; echo \"---spring boot version / jackson date---\"; grep -rn \"spring.jackson\\\\|jackson\" \"C:/Users/admin/Downloads/eduepos_server-master/eduepos_server/eduepos-group/client-app/src/main/resources/application.yml\" 2>/dev/null)",
+      "Read(//c/Users/admin/Downloads/eduepos_server-master/eduepos_server/eduepos-group/client-app/src/main/resources/**)",
+      "Bash(grep -n \"DateEditor\\\\|@InitBinder\\\\|WebDataBinder\\\\|registerCustomEditor\\\\|SimpleDateFormat\" \"C:/Users/admin/Downloads/eduepos_server-master/eduepos_server/eduepos-group/client-app/src/main/java/com/sapit/client/app/manage/controller/rider/DeliveryRiderManage1Controller.java\" | head; echo \"---any global binder---\"; grep -rn \"@ControllerAdvice\\\\|@InitBinder\" \"C:/Users/admin/Downloads/eduepos_server-master/eduepos_server/eduepos-group/client-app/src/main/java/com/sapit/client/app\" --include=*.java | grep -i \"binder\\\\|advice\" | head)",
+      "Bash(cd \"C:/Users/admin/Downloads/eduepos_server-master/eduepos_server/eduepos-group\"; grep -rln \"WebMvcConfigurer\\\\|@ControllerAdvice\\\\|addFormatters\\\\|GlobalDateConverter\\\\|StringToDate\" --include=*.java client-app common framework 2>/dev/null | head; echo \"---search for custom date converter registration---\"; grep -rn \"@Bean.*Converter\\\\|implements Converter\" --include=*.java . 2>/dev/null | grep -i date | head)",
+      "Bash(cd \"C:/Users/admin/Downloads/eduepos_server-master/eduepos_server/eduepos-group\"; grep -n \"Date\\\\|Formatter\\\\|Binder\\\\|ConversionService\\\\|WebMvcConfigurer\" common/src/main/java/com/sapit/common/config/WebAppConfiguration.java | head -20)",
+      "Bash(cd \"C:/Users/admin/Downloads/eduepos_server-master/eduepos_server/eduepos-group\"; grep -rn \"spring-boot\\\\|spring.version\\\\|<spring\" pom.xml | head; echo \"---delivery order date format---\"; cd \"c:/Users/admin/Downloads/eduepos_ui_manage-master/eduepos_ui_manage\"; grep -n \"value-format\\\\|format\\(\" src/components/page/admin/delivery/DeliveryOrderManage1.vue | head)",
+      "Bash('/c/Program Files/MySQL/MySQL Workbench 8.0 CE/mysql' -h192.168.200.4 -P3306 -uroot -pAbcd@1234 -N -e ' *)",
+      "Bash(\"/c/Program Files/MySQL/MySQL Workbench 8.0 CE/mysql\" -h192.168.200.4 -P3306 -uroot -p'Abcd@1234' -N -e \"SHOW TABLES FROM \\\\`eduepos\\\\` LIKE 'bu_inspect%';\")",
+      "Bash(grep -oE \"^\\\\s+\\\\`[A-Z_]+\\\\`\")",
+      "Bash(\"/c/Program Files/MySQL/MySQL Workbench 8.0 CE/mysql\" -h192.168.200.4 -P3306 -uroot -p'Abcd@1234' \"eduepos\")",
+      "Bash(\"/c/Program Files/MySQL/MySQL Workbench 8.0 CE/mysql\" -h192.168.200.4 -P3306 -uroot -p'Abcd@1234' -N -e \"SHOW TABLES FROM \\\\`eduepos\\\\` LIKE 'bu_inspect%'; SHOW TABLES FROM \\\\`eduepos\\\\` LIKE 'bu_assess%'; SHOW COLUMNS FROM \\\\`eduepos\\\\`.bu_inspect_indicator LIKE 'WEIGHT';\")"
     ],
     "additionalDirectories": [
-      "C:\\Users\\admin"
+      "C:\\Users\\admin",
+      "C:\\Users\\admin\\Downloads\\eduepos_ui_manage-master\\eduepos_ui_manage\\src\\components\\page\\admin\\dataStatistics"
     ]
   }
 }

+ 1 - 1
src/components/common/axios.js

@@ -17,7 +17,7 @@ axios.interceptors.request.use(config => {
         }
     }
     loadinginstace = Loading.service({ fullscreen: true });
-    var token = localStorage.getItem('token');
+    var token = localStorage.getItem('Authorization') || localStorage.getItem('token');
     //if (store.state.token) {
     if (token) {
         config.headers.Authorization = token;

+ 3 - 1
src/components/page/admin/Login.vue

@@ -141,7 +141,9 @@ export default {
                     } else {
                         this.$message.success("登录成功!");
                         localStorage.setItem("am_username", this.loginForm.usercode);
-                        localStorage.setItem("token", res.headers.authorization || '');
+                        const token = res.headers.authorization || '';
+                        localStorage.setItem("token", token);
+                        localStorage.setItem("Authorization", token);
                         localStorage.setItem("userbean", JSON.stringify(res.data.obj || {}));
                         const obj = res.data.obj || {};
                         if (obj.areaList && obj.areaList.length > 0) {

+ 52 - 0
src/components/page/admin/inspect/InspectManage.vue

@@ -0,0 +1,52 @@
+<template>
+  <div class="inspect-page">
+    <div class="head">
+      <div>
+        <h2>门店巡检与考核</h2>
+        <p>指标配置 · 计划管理 · 任务执行 · 整改流程 · 考核排名</p>
+      </div>
+      <el-button type="primary" icon="el-icon-refresh" @click="reload">刷新当前页</el-button>
+    </div>
+    <el-tabs v-model="tab" type="card" @tab-click="reload">
+      <el-tab-pane label="考核指标配置" name="indicator"><indicator-index ref="indicator" /></el-tab-pane>
+      <el-tab-pane label="巡检计划管理" name="plan"><plan-index ref="plan" /></el-tab-pane>
+      <el-tab-pane label="巡检任务执行" name="task"><task-index ref="task" /></el-tab-pane>
+      <el-tab-pane label="整改流程管理" name="rect"><rectification-index ref="rect" /></el-tab-pane>
+      <el-tab-pane label="考核结果排名" name="assess"><assess-index ref="assess" /></el-tab-pane>
+    </el-tabs>
+  </div>
+</template>
+
+<script>
+import IndicatorIndex from './indicator/Index.vue'
+import PlanIndex from './plan/Index.vue'
+import TaskIndex from './task/Index.vue'
+import RectificationIndex from './rectification/Index.vue'
+import AssessIndex from './assess/Index.vue'
+
+export default {
+  name: 'InspectManage',
+  components: { IndicatorIndex, PlanIndex, TaskIndex, RectificationIndex, AssessIndex },
+  data() {
+    return { tab: 'indicator' }
+  },
+  methods: {
+    reload() {
+      this.$nextTick(() => {
+        const page = this.$refs[this.tab]
+        if (page) {
+          if (typeof page.loadAll === 'function') page.loadAll()
+          else if (typeof page.load === 'function') page.load()
+        }
+      })
+    }
+  }
+}
+</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; }
+</style>

+ 214 - 0
src/components/page/admin/inspect/assess/Detail.vue

@@ -0,0 +1,214 @@
+<template>
+  <el-dialog title="门店考核详情" :visible.sync="dialogVisible" width="860px" @open="load">
+    <div class="detail-wrap" v-loading="loading">
+      <div class="head" v-if="row">
+        <div class="head-main">
+          {{ row.storeName }}
+          <span class="rank-badge" :class="rankClass(row.rankNo)">{{ row.rankNo || '-' }}</span>
+          <el-tag size="small" :type="levelMeta(row.assessLevel).type" class="head-tag">{{ levelMeta(row.assessLevel).text }}</el-tag>
+        </div>
+        <div class="head-meta">
+          <span v-if="row.storeBelongto">校区:{{ belongtoName(row.storeBelongto) }}</span>
+          <span>{{ periodTypeText(row.periodType) }}周期:{{ row.periodCode }}</span>
+          <span v-if="row.periodStartDate">周期范围:{{ fmt(row.periodStartDate) }} ~ {{ fmt(row.periodEndDate) }}</span>
+        </div>
+      </div>
+
+      <!-- 得分构成 -->
+      <div class="block">
+        <h4>得分构成</h4>
+        <div class="score-comp" v-if="row">
+          <div class="comp-item">
+            <span class="comp-label">巡检分</span>
+            <div class="comp-track">
+              <div class="comp-bar" :style="{ width: pct(row.inspectScore) + '%', background: '#409EFF' }"></div>
+            </div>
+            <b class="comp-val">{{ fmtNum(row.inspectScore) }}</b>
+          </div>
+          <div class="comp-item">
+            <span class="comp-label">自动分</span>
+            <div class="comp-track">
+              <div class="comp-bar" :style="{ width: pct(row.autoScore) + '%', background: '#67C23A' }"></div>
+            </div>
+            <b class="comp-val">{{ fmtNum(row.autoScore) }}</b>
+          </div>
+          <div class="comp-item">
+            <span class="comp-label">综合分</span>
+            <div class="comp-track">
+              <div class="comp-bar" :style="{ width: pct(row.totalScore) + '%', background: '#E6A23C' }"></div>
+            </div>
+            <b class="comp-val total">{{ fmtNum(row.totalScore) }}</b>
+          </div>
+        </div>
+        <div class="level-line" v-if="row">
+          <span>考核等级:</span>
+          <el-tag size="mini" :type="levelMeta(row.assessLevel).type">{{ levelMeta(row.assessLevel).text }}</el-tag>
+          <span class="stat-line">任务 {{ row.taskCount || 0 }} · 已完成 {{ row.finishTaskCount || 0 }} · 整改 {{ row.rectCount || 0 }} · 超期整改 {{ row.overtimeRectCount || 0 }}</span>
+        </div>
+      </div>
+
+      <!-- 历史趋势 -->
+      <div class="block">
+        <h4>历史得分趋势</h4>
+        <div class="mini-chart" v-if="trend.length">
+          <div v-for="t in trend" :key="t.periodCode" class="mini-bar-wrap">
+            <div class="mini-bar" :style="{ height: Math.max(6, (t.totalScore / 100) * 90) + '%' }"></div>
+            <span class="mini-val">{{ fmtNum(t.totalScore) }}</span>
+            <span class="mini-label">{{ t.periodCode }}</span>
+          </div>
+        </div>
+        <div class="muted" v-else>暂无历史数据</div>
+      </div>
+
+      <!-- 巡检任务 -->
+      <div class="block">
+        <h4>周期内巡检任务({{ tasks.length }})</h4>
+        <el-table :data="tasks" border size="mini" max-height="220">
+          <el-table-column prop="taskId" label="ID" width="70" />
+          <el-table-column prop="planName" label="计划" min-width="120" />
+          <el-table-column label="执行日期" width="110">
+            <template slot-scope="s">{{ fmt(s.row.taskDate) }}</template>
+          </el-table-column>
+          <el-table-column label="得分" width="90" align="center">
+            <template slot-scope="s">{{ fmtNum(s.row.totalScore) }}/{{ fmtNum(s.row.maxScore) }}</template>
+          </el-table-column>
+          <el-table-column label="结果" width="75">
+            <template slot-scope="s">
+              <el-tag v-if="s.row.status === 3" size="mini" :type="s.row.passFlag === 1 ? 'success' : 'danger'">{{ s.row.passFlag === 1 ? '及格' : '不及格' }}</el-tag>
+              <span v-else class="muted">-</span>
+            </template>
+          </el-table-column>
+        </el-table>
+      </div>
+
+      <!-- 整改记录 -->
+      <div class="block">
+        <h4>整改记录({{ rects.length }})</h4>
+        <el-table :data="rects" border size="mini" max-height="220">
+          <el-table-column prop="indicatorName" label="指标" min-width="120" />
+          <el-table-column label="轮次" width="70" align="center">
+            <template slot-scope="s">第 {{ s.row.rectRound || 1 }} 轮</template>
+          </el-table-column>
+          <el-table-column label="截止日期" width="115">
+            <template slot-scope="s">{{ fmt(s.row.deadlineDate, true) }}</template>
+          </el-table-column>
+          <el-table-column label="状态" width="100">
+            <template slot-scope="s">
+              <el-tag size="mini" :style="rectStyle(s.row.status)">{{ rectText(s.row.status) }}</el-tag>
+            </template>
+          </el-table-column>
+        </el-table>
+      </div>
+    </div>
+    <span slot="footer">
+      <el-button @click="dialogVisible = false">关闭</el-button>
+    </span>
+  </el-dialog>
+</template>
+
+<script>
+import { post, fmt, fmtNum, slashDate, cleanParams, ASSESS_LEVEL, RECT_STATUS } from '../common'
+
+export default {
+  name: 'InspectAssessDetail',
+  props: {
+    visible: Boolean,
+    row: Object
+  },
+  computed: {
+    dialogVisible: {
+      get() { return this.visible },
+      set(v) { this.$emit('update:visible', v) }
+    }
+  },
+  data() {
+    return { loading: false, tasks: [], rects: [], trend: [], stores: [] }
+  },
+  methods: {
+    load() {
+      if (!this.row) return
+      this.loading = true
+      const start = this.row.periodStartDate
+        ? slashDate((this.row.periodStartDate || '').slice ? this.row.periodStartDate : fmt(this.row.periodStartDate))
+        : ''
+      const end = this.row.periodEndDate
+        ? slashDate((this.row.periodEndDate || '').slice ? this.row.periodEndDate : fmt(this.row.periodEndDate))
+        : ''
+      Promise.all([
+        post(this.$axios, '/inspectManage/task/page', cleanParams({ storeId: this.row.storeId, taskDateBegin: start, taskDateEnd: end, pageSize: 50, currentPage: 1 })),
+        post(this.$axios, '/inspectManage/rectification/page', { storeId: this.row.storeId, pageSize: 50, currentPage: 1 }),
+        post(this.$axios, '/inspectManage/assess/trend', { storeIds: [this.row.storeId], periodType: this.row.periodType || '' }),
+        post(this.$axios, '/storeManage/getStoreList', {})
+      ]).then(([r1, r2, r3, r4]) => {
+        this.stores = (r4 && r4.list) || []
+        this.tasks = (r1 && r1.list) || []
+        this.rects = (r2 && r2.list) || []
+        const rows = (r3 && r3.list) || []
+        this.trend = rows.map(row => {
+          const arr = Array.isArray(row) ? row : Object.keys(row).map(k => row[k])
+          return {
+            storeId: String(arr[0]),
+            periodCode: arr[1] === null || arr[1] === undefined ? '' : String(arr[1]),
+            totalScore: Number(arr[2] || 0)
+          }
+        })
+      }).finally(() => { this.loading = false })
+    },
+    pct(v) {
+      const n = Number(v || 0)
+      return Math.max(0, Math.min(100, n))
+    },
+    rankClass(v) {
+      const n = Number(v || 0)
+      if (n === 1) return 'rank-1'
+      if (n === 2) return 'rank-2'
+      if (n === 3) return 'rank-3'
+      return 'rank-n'
+    },
+    belongtoName(v) {
+      if (v === null || v === undefined || v === '') return '-'
+      const st = this.stores.find(s => String(s.storeBelongto) === String(v))
+      return st ? (st.storeBelongtoName || '-') : '-'
+    },
+    periodTypeText(v) { return v === 1 ? '周度' : (v === 2 ? '月度' : '-') },
+    levelMeta(v) { return ASSESS_LEVEL[v] || { text: '-', type: 'info' } },
+    rectText(v) { return (RECT_STATUS[v] || {}).text || '-' },
+    rectStyle(v) {
+      const st = RECT_STATUS[v]
+      return { background: st.color, borderColor: st.color, color: '#fff' }
+    },
+    fmt,
+    fmtNum
+  }
+}
+</script>
+
+<style scoped>
+.detail-wrap { max-height: 560px; overflow-y: auto; }
+.head { background: #f5f7fa; border-radius: 4px; padding: 12px 16px; margin-bottom: 16px; }
+.head-main { font-size: 16px; font-weight: 600; margin-bottom: 6px; display: flex; align-items: center; gap: 10px; }
+.head-tag { flex-shrink: 0; }
+.head-meta { display: flex; gap: 18px; color: #606266; font-size: 13px; flex-wrap: wrap; }
+.rank-badge { display: inline-flex; align-items: center; justify-content: center; width: 24px; height: 24px; border-radius: 50%; font-size: 12px; font-weight: 700; flex-shrink: 0; }
+.rank-1 { background: #f56c6c; color: #fff; }
+.rank-2 { background: #e6a23c; color: #fff; }
+.rank-3 { background: #f7ba2a; color: #fff; }
+.rank-n { background: #f0f2f5; color: #606266; }
+.block { margin-bottom: 18px; }
+.block h4 { margin: 0 0 10px; font-size: 13px; color: #606266; }
+.score-comp { display: flex; flex-direction: column; gap: 10px; }
+.comp-item { display: flex; align-items: center; gap: 10px; }
+.comp-label { width: 56px; color: #909399; font-size: 13px; }
+.comp-track { flex: 1; height: 12px; background: #f0f2f5; border-radius: 6px; overflow: hidden; }
+.comp-bar { height: 100%; border-radius: 6px; transition: width .3s; }
+.comp-val { width: 56px; color: #606266; font-size: 13px; text-align: right; }
+.comp-val.total { color: #e6a23c; font-weight: 700; }
+.level-line { margin-top: 12px; display: flex; align-items: center; gap: 10px; font-size: 13px; color: #606266; }
+.stat-line { color: #909399; }
+.mini-chart { display: flex; align-items: flex-end; gap: 14px; height: 120px; padding: 4px 0 0; }
+.mini-bar-wrap { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: flex-end; height: 100%; }
+.mini-bar { width: 26px; background: #409eff; border-radius: 3px 3px 0 0; }
+.mini-val { font-size: 11px; color: #606266; margin-top: 4px; }
+.mini-label { font-size: 11px; color: #909399; margin-top: 2px; }
+.muted { color: #909399; }
+</style>

+ 455 - 0
src/components/page/admin/inspect/assess/Index.vue

@@ -0,0 +1,455 @@
+<template>
+  <section class="panel">
+    <div class="toolbar">
+      <el-select v-model="query.periodType" placeholder="考核周期" clearable style="width: 130px;">
+        <el-option label="周度" :value="1" />
+        <el-option label="月度" :value="2" />
+      </el-select>
+      <el-input v-model="query.periodCode" placeholder="周期编码,如 2026-08" clearable style="width: 180px;" />
+      <el-select v-model="query.storeBelongto" placeholder="校区/区域" clearable filterable style="width: 180px;">
+        <el-option v-for="b in belongtoOptions" :key="b.value" :label="b.label" :value="b.value" />
+      </el-select>
+      <el-select v-model="query.assessLevel" placeholder="考核等级" clearable style="width: 130px;">
+        <el-option label="优秀" :value="1" />
+        <el-option label="良好" :value="2" />
+        <el-option label="合格" :value="3" />
+        <el-option label="不合格" :value="4" />
+      </el-select>
+      <el-input v-model="storeNameKw" placeholder="门店名称" clearable style="width: 160px;" />
+      <el-button type="primary" @click="loadAll">查询</el-button>
+      <el-button @click="reset">重置</el-button>
+      <span class="grow"></span>
+      <el-button type="warning" plain @click="updateRank">更新排名</el-button>
+      <el-button type="success" plain @click="exportData">导出报告</el-button>
+      <el-button type="primary" @click="triggerVisible = true">手动汇总</el-button>
+    </div>
+
+    <!-- 统计卡片 -->
+    <div class="stat-cards">
+      <div class="stat-card"><span>参评门店</span><b>{{ stat.storeCount }}</b><em>家</em></div>
+      <div class="stat-card"><span>平均综合分</span><b>{{ stat.avgTotal }}</b><em>分</em></div>
+      <div class="stat-card good"><span>优秀门店</span><b>{{ stat.excellent }}</b><em>家</em></div>
+      <div class="stat-card fail"><span>不合格门店</span><b>{{ stat.fail }}</b><em>家</em></div>
+    </div>
+
+    <div class="charts-row">
+      <!-- 等级分布 -->
+      <div class="chart-panel">
+        <h4>考核等级分布</h4>
+        <div class="donut-wrap" v-if="distTotal > 0">
+          <div class="donut" :style="donutStyle"></div>
+          <div class="donut-center">{{ distTotal }}</div>
+        </div>
+        <div class="dist-legend">
+          <div v-for="d in distributions" :key="d.level" class="dist-item">
+            <span class="dot" :style="{ background: levelColor(d.level) }"></span>
+            <span>{{ levelText(d.level) }}</span>
+            <b>{{ d.count }}</b>
+            <em>{{ distPercent(d.count) }}%</em>
+          </div>
+          <div v-if="!distTotal" class="muted">暂无数据</div>
+        </div>
+      </div>
+
+      <!-- 趋势对比 -->
+      <div class="chart-panel grow">
+        <h4>门店考核趋势对比</h4>
+        <div class="trend-toolbar">
+          <el-select v-model="trendStoreIds" multiple filterable placeholder="选择对比门店(可多选)" style="width: 320px;" @change="loadTrend">
+            <el-option v-for="s in trendStoreOptions" :key="s.storeId" :label="s.storeName" :value="s.storeId" />
+          </el-select>
+          <el-button size="mini" @click="loadTrend">刷新趋势</el-button>
+        </div>
+        <div class="trend-chart" v-if="trendSeries.length">
+          <svg :viewBox="'0 0 ' + chartW + ' ' + chartH" class="trend-svg">
+            <g v-for="g in gridLines" :key="g.y">
+              <line :x1="padL" :y1="g.y" :x2="chartW - padR" :y2="g.y" class="grid-line" />
+              <text :x="padL - 6" :y="g.y + 4" class="grid-text">{{ g.label }}</text>
+            </g>
+            <g v-for="cat in trendCats" :key="cat">
+              <text :x="cat.x" :y="chartH - padB + 14" class="grid-text cat-text" text-anchor="middle">{{ cat.label }}</text>
+            </g>
+            <polyline
+              v-for="s in trendSeries"
+              :key="s.storeId"
+              :points="s.pointsText"
+              fill="none"
+              :stroke="s.color"
+              stroke-width="2" />
+            <circle
+              v-for="p in allPoints"
+              :key="p.key"
+              :cx="p.x"
+              :cy="p.y"
+              r="3"
+              :fill="p.color" />
+          </svg>
+        </div>
+        <div class="trend-legend">
+          <span v-for="s in trendSeries" :key="s.storeId" class="legend-item">
+            <i :style="{ background: s.color }"></i>{{ s.storeName }}
+          </span>
+          <span v-if="!trendSeries.length" class="muted">请选择门店查看得分趋势</span>
+        </div>
+      </div>
+    </div>
+
+    <!-- 排名表 -->
+    <div class="rank-block">
+      <h4>考核排名({{ filteredRows.length }} 家门店)</h4>
+      <el-table :data="filteredRows" border stripe v-loading="loading" @row-click="openDetail">
+        <el-table-column label="排名" width="60" align="center">
+          <template slot-scope="s">
+            <span class="rank-badge" :class="rankClass(s.row.rankNo)">{{ s.row.rankNo || '-' }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="storeName" label="门店" min-width="140" />
+        <el-table-column label="校区" width="110">
+          <template slot-scope="s">{{ belongtoName(s.row.storeBelongto) }}</template>
+        </el-table-column>
+        <el-table-column label="巡检分" width="85" align="center">
+          <template slot-scope="s">{{ fmtNum(s.row.inspectScore) }}</template>
+        </el-table-column>
+        <el-table-column label="自动分" width="85" align="center">
+          <template slot-scope="s">{{ fmtNum(s.row.autoScore) }}</template>
+        </el-table-column>
+        <el-table-column label="综合分" width="100" align="center">
+          <template slot-scope="s">
+            <span class="total-score">{{ fmtNum(s.row.totalScore) }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="等级" width="85">
+          <template slot-scope="s"><el-tag size="mini" :type="levelMeta(s.row.assessLevel).type">{{ levelMeta(s.row.assessLevel).text }}</el-tag></template>
+        </el-table-column>
+        <el-table-column prop="taskCount" label="巡检次数" width="80" align="center" />
+        <el-table-column label="及格率" width="75" align="center">
+          <template slot-scope="s">{{ passRate(s.row) }}</template>
+        </el-table-column>
+        <el-table-column prop="rectCount" label="整改数" width="70" align="center" />
+        <el-table-column prop="overtimeRectCount" label="超期整改" width="80" align="center">
+          <template slot-scope="s"><span :class="{ 'overtime': s.row.overtimeRectCount > 0 }">{{ s.row.overtimeRectCount || 0 }}</span></template>
+        </el-table-column>
+        <el-table-column label="操作" width="80" fixed="right">
+          <template slot-scope="s"><el-button type="text" @click.stop="openDetail(s.row)">详情</el-button></template>
+        </el-table-column>
+      </el-table>
+      <div class="empty" v-if="!filteredRows.length && !loading">暂无考核数据,可点击“手动汇总”或等待定时任务生成。</div>
+    </div>
+
+    <assess-detail :visible.sync="detailVisible" :row="detailRow" @saved="loadAll" />
+
+    <!-- 手动汇总 -->
+    <el-dialog title="手动汇总考核数据" :visible.sync="triggerVisible" width="480px">
+      <el-form :model="triggerForm" label-width="100px">
+        <el-form-item label="考核周期" required>
+          <el-radio-group v-model="triggerForm.periodType">
+            <el-radio :label="1">周度</el-radio>
+            <el-radio :label="2">月度</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label="周期编码" required>
+          <el-input v-model="triggerForm.periodCode" placeholder="如 2026-08" />
+        </el-form-item>
+        <el-form-item label="周期范围" required>
+          <el-date-picker v-model="periodRange" type="daterange" value-format="yyyy-MM-dd" start-placeholder="开始日期" end-placeholder="结束日期" style="width: 100%;" />
+        </el-form-item>
+        <el-form-item>
+          <span class="tip">汇总将按周期内已完成任务计算各门店巡检分、自动分与综合分,并生成排名。</span>
+        </el-form-item>
+      </el-form>
+      <span slot="footer">
+        <el-button @click="triggerVisible = false">取消</el-button>
+        <el-button type="primary" :loading="triggering" @click="trigger">开始汇总</el-button>
+      </span>
+    </el-dialog>
+  </section>
+</template>
+
+<script>
+import { post, ok, msg, fmtNum, cleanParams, ASSESS_LEVEL } from '../common'
+import AssessDetail from './Detail.vue'
+
+const LEVEL_COLORS = { 1: '#67C23A', 2: '#409EFF', 3: '#E6A23C', 4: '#F56C6C' }
+
+export default {
+  name: 'InspectAssessIndex',
+  components: { AssessDetail },
+  data() {
+    return {
+      query: { periodType: '', periodCode: '', storeBelongto: '', assessLevel: '', pageSize: 500, currentPage: 1 },
+      storeNameKw: '',
+      rows: [],
+      loading: false,
+      stores: [],
+      distributions: [],
+      trendStoreIds: [],
+      trendRows: [],
+      detailVisible: false,
+      detailRow: null,
+      triggerVisible: false,
+      triggering: false,
+      periodRange: [],
+      triggerForm: { periodType: 2, periodCode: '', periodStartDate: '', periodEndDate: '' },
+      // 图表参数
+      padL: 40, padR: 16, padT: 12, padB: 26, chartW: 560, chartH: 200
+    }
+  },
+  computed: {
+    belongtoOptions() {
+      const map = {}
+      this.stores.forEach(s => {
+        if (s.storeBelongtoName) map[s.storeBelongtoName] = s.storeBelongto || ''
+      })
+      return Object.keys(map).map(k => ({ label: k, value: map[k] }))
+    },
+    trendStoreOptions() {
+      return this.rows.map(r => ({ storeId: r.storeId, storeName: r.storeName }))
+    },
+    filteredRows() {
+      const kw = (this.storeNameKw || '').trim()
+      if (!kw) return this.rows
+      return this.rows.filter(r => (r.storeName || '').indexOf(kw) !== -1)
+    },
+    stat() {
+      const rows = this.filteredRows
+      const total = rows.length
+      const avg = total ? rows.reduce((s, r) => s + (Number(r.totalScore) || 0), 0) / total : 0
+      return {
+        storeCount: total,
+        avgTotal: avg.toFixed(1),
+        excellent: rows.filter(r => r.assessLevel === 1).length,
+        fail: rows.filter(r => r.assessLevel === 4).length
+      }
+    },
+    distTotal() {
+      return this.distributions.reduce((s, d) => s + d.count, 0)
+    },
+    donutStyle() {
+      if (!this.distTotal) return {}
+      let acc = 0
+      const stops = this.distributions.map(d => {
+        const start = acc / this.distTotal * 360
+        acc += d.count
+        const end = acc / this.distTotal * 360
+        return LEVEL_COLORS[d.level] + ' ' + start.toFixed(1) + 'deg ' + end.toFixed(1) + 'deg'
+      })
+      return { background: 'conic-gradient(' + stops.join(',') + ')' }
+    },
+    trendCats() {
+      const cats = this.uniqPeriodCodes
+      const W = this.chartW - this.padL - this.padR
+      return cats.map((c, i) => ({
+        label: c,
+        x: cats.length === 1 ? this.padL + W / 2 : this.padL + (i / (cats.length - 1)) * W,
+        value: c
+      }))
+    },
+    uniqPeriodCodes() {
+      const set = {}
+      this.trendRows.forEach(r => { set[r.periodCode] = 1 })
+      return Object.keys(set).sort()
+    },
+    trendSeries() {
+      const cats = this.uniqPeriodCodes
+      const H = this.chartH - this.padT - this.padB
+      const W = this.chartW - this.padL - this.padR
+      const maxY = Math.max(100, ...this.trendRows.map(r => Number(r.totalScore) || 0))
+      const colors = ['#409EFF', '#67C23A', '#E6A23C', '#F56C6C', '#9B59B6', '#00BCD4', '#FF9800', '#8BC34A']
+      return this.trendStoreIds.map((sid, si) => {
+        const points = cats.map((c, i) => {
+          const row = this.trendRows.find(r => String(r.storeId) === String(sid) && String(r.periodCode) === String(c))
+          const val = row ? (Number(row.totalScore) || 0) : 0
+          const x = cats.length === 1 ? this.padL + W / 2 : this.padL + (i / (cats.length - 1)) * W
+          const y = this.padT + H - (val / maxY) * H
+          return { x, y, val }
+        })
+        return {
+          storeId: sid,
+          storeName: (this.trendStoreOptions.find(o => String(o.storeId) === String(sid)) || {}).storeName || sid,
+          color: colors[si % colors.length],
+          points,
+          pointsText: points.map(p => p.x.toFixed(1) + ',' + p.y.toFixed(1)).join(' ')
+        }
+      })
+    },
+    allPoints() {
+      const list = []
+      this.trendSeries.forEach(s => {
+        s.points.forEach(p => {
+          list.push({ key: s.storeId + '-' + p.x, x: p.x, y: p.y, color: s.color })
+        })
+      })
+      return list
+    },
+    gridLines() {
+      const lines = []
+      for (let i = 0; i <= 4; i++) {
+        const y = this.padT + (this.chartH - this.padT - this.padB) * (i / 4)
+        lines.push({ y, label: 100 - i * 25 })
+      }
+      return lines
+    }
+  },
+  created() {
+    this.loadStores()
+    this.loadAll()
+  },
+  methods: {
+    loadStores() {
+      post(this.$axios, '/storeManage/getStoreList', {}).then(res => { this.stores = (res && res.list) || [] })
+    },
+    loadAll() {
+      this.loading = true
+      post(this.$axios, '/inspectManage/assess/page', cleanParams(this.query)).then(res => {
+        this.rows = (res && res.list) || []
+      }).finally(() => { this.loading = false })
+      this.loadDistribution()
+      this.loadTrend()
+    },
+    loadDistribution() {
+      if (!this.query.periodType && !this.query.periodCode) {
+        this.distributions = []
+        return
+      }
+      post(this.$axios, '/inspectManage/assess/levelDistribution', { periodType: this.query.periodType || '', periodCode: this.query.periodCode || '' }).then(res => {
+        const list = (res && res.list) || []
+        this.distributions = list.map(row => {
+          const arr = Array.isArray(row) ? row : Object.keys(row).map(k => row[k])
+          return { level: Number(arr[0]), count: Number(arr[1] || 0) }
+        }).filter(d => !isNaN(d.level))
+      })
+    },
+    loadTrend() {
+      if (!this.trendStoreIds.length) {
+        this.trendRows = []
+        return
+      }
+      post(this.$axios, '/inspectManage/assess/trend', { storeIds: this.trendStoreIds, periodType: this.query.periodType || '' }).then(res => {
+        const list = (res && res.list) || []
+        this.trendRows = list.map(row => {
+          const arr = Array.isArray(row) ? row : Object.keys(row).map(k => row[k])
+          return { storeId: String(arr[0]), periodCode: String(arr[1] === null || arr[1] === undefined ? '' : arr[1]), totalScore: Number(arr[2] || 0) }
+        })
+      })
+    },
+    updateRank() {
+      if (!this.query.periodType || !this.query.periodCode) return this.$message.warning('请先选择考核周期与周期编码')
+      post(this.$axios, '/inspectManage/assess/rank', { periodType: this.query.periodType, periodCode: this.query.periodCode }).then(res => {
+        if (ok(res)) { this.loadAll(); this.$message.success('排名已更新') } else { this.$message.error(msg(res) || '更新失败') }
+      })
+    },
+    exportData() {
+      if (!this.query.periodType || !this.query.periodCode) return this.$message.warning('请先选择考核周期与周期编码')
+      this.$axios.post('/inspectManage/assess/export', require('querystring').stringify({ periodType: this.query.periodType, periodCode: this.query.periodCode }), { responseType: 'blob' }).then(res => {
+        const blob = new Blob([res.data], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' })
+        const elink = document.createElement('a')
+        elink.download = '考核排名-' + this.query.periodCode + '.xlsx'
+        elink.style.display = 'none'
+        elink.href = URL.createObjectURL(blob)
+        document.body.appendChild(elink)
+        elink.click()
+        URL.revokeObjectURL(elink.href)
+        document.body.removeChild(elink)
+      }).catch(() => this.$message.error('导出失败'))
+    },
+    reset() {
+      this.query = { periodType: '', periodCode: '', storeBelongto: '', assessLevel: '', pageSize: 500, currentPage: 1 }
+      this.storeNameKw = ''
+      this.loadAll()
+    },
+    openDetail(row) {
+      this.detailRow = row
+      this.detailVisible = true
+    },
+    trigger() {
+      if (!this.triggerForm.periodCode) return this.$message.warning('请输入周期编码')
+      if (!this.periodRange || !this.periodRange.length) return this.$message.warning('请选择周期范围')
+      const form = Object.assign({}, this.triggerForm, { periodStartDate: this.periodRange[0], periodEndDate: this.periodRange[1] })
+      this.triggering = true
+      post(this.$axios, '/inspectManage/assess/trigger', form).then(res => {
+        if (ok(res)) {
+          const count = (res && res.obj) || 0
+          this.$message.success('汇总完成,共汇总 ' + count + ' 家门店')
+          this.triggerVisible = false
+          this.query.periodType = form.periodType
+          this.query.periodCode = form.periodCode
+          this.loadAll()
+        } else {
+          this.$message.error(msg(res) || '汇总失败')
+        }
+      }).finally(() => { this.triggering = false })
+    },
+    distPercent(n) {
+      if (!this.distTotal) return 0
+      return (n / this.distTotal * 100).toFixed(1)
+    },
+    rankClass(v) {
+      const n = Number(v || 0)
+      if (n === 1) return 'rank-1'
+      if (n === 2) return 'rank-2'
+      if (n === 3) return 'rank-3'
+      return 'rank-n'
+    },
+    belongtoName(v) {
+      if (v === null || v === undefined || v === '') return '-'
+      const st = this.stores.find(s => String(s.storeBelongto) === String(v))
+      return st ? (st.storeBelongtoName || '-') : '-'
+    },
+    passRate(row) {
+      const total = Number(row.taskCount || 0)
+      if (!total) return '-'
+      return Math.round(Number(row.finishTaskCount || 0) / total * 100) + '%'
+    },
+    levelColor(v) { return LEVEL_COLORS[v] || '#909399' },
+    levelText(v) { return (ASSESS_LEVEL[v] || {}).text || '-' },
+    levelMeta(v) { return ASSESS_LEVEL[v] || { text: '-', type: 'info' } },
+    fmtNum
+  }
+}
+</script>
+
+<style scoped>
+.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-select, .toolbar .el-input { width: 160px; }
+.grow { flex: 1; }
+.stat-cards { display: flex; gap: 16px; margin-bottom: 16px; }
+.stat-card { flex: 1; background: #f5f7fa; border-radius: 6px; padding: 16px 20px; display: flex; align-items: baseline; gap: 8px; }
+.stat-card span { color: #909399; font-size: 13px; }
+.stat-card b { font-size: 28px; color: #303133; }
+.stat-card em { color: #909399; font-style: normal; font-size: 12px; }
+.stat-card.good b { color: #67c23a; }
+.stat-card.fail b { color: #f56c6c; }
+.charts-row { display: flex; gap: 16px; margin-bottom: 16px; }
+.chart-panel { border: 1px solid #ebeef5; border-radius: 6px; padding: 14px; width: 300px; }
+.chart-panel.grow { flex: 1; width: auto; }
+.chart-panel h4 { margin: 0 0 12px; font-size: 14px; color: #303133; }
+.donut-wrap { position: relative; width: 160px; height: 160px; margin: 0 auto 12px; }
+.donut { width: 160px; height: 160px; border-radius: 50%; }
+.donut-center { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 96px; height: 96px; background: #fff; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 26px; font-weight: 600; color: #303133; }
+.dist-legend { display: flex; flex-direction: column; gap: 8px; }
+.dist-item { display: flex; align-items: center; gap: 8px; font-size: 13px; color: #606266; }
+.dist-item b { margin-left: auto; }
+.dist-item em { color: #909399; font-style: normal; width: 44px; text-align: right; }
+.dot { width: 10px; height: 10px; border-radius: 50%; }
+.trend-toolbar { display: flex; gap: 10px; align-items: center; margin-bottom: 10px; }
+.trend-chart { height: 200px; }
+.trend-svg { width: 100%; height: 200px; }
+.grid-line { stroke: #ebeef5; stroke-width: 1; }
+.grid-text { fill: #909399; font-size: 11px; }
+.trend-legend { display: flex; gap: 14px; flex-wrap: wrap; margin-top: 8px; }
+.legend-item { display: flex; align-items: center; gap: 5px; font-size: 12px; color: #606266; }
+.legend-item i { width: 12px; height: 3px; border-radius: 2px; }
+.rank-block { border: 1px solid #ebeef5; border-radius: 6px; padding: 14px; }
+.rank-block h4 { margin: 0 0 12px; font-size: 14px; color: #303133; }
+.rank-badge { display: inline-flex; align-items: center; justify-content: center; width: 24px; height: 24px; border-radius: 50%; font-size: 12px; font-weight: 700; }
+.rank-1 { background: #f56c6c; color: #fff; }
+.rank-2 { background: #e6a23c; color: #fff; }
+.rank-3 { background: #f7ba2a; color: #fff; }
+.rank-n { background: #f0f2f5; color: #606266; }
+.total-score { color: #409eff; font-weight: 600; }
+.overtime { color: #f56c6c; }
+.empty { color: #909399; padding: 24px; text-align: center; }
+.muted { color: #909399; }
+.tip { color: #909399; font-size: 12px; line-height: 1.6; }
+</style>

+ 161 - 0
src/components/page/admin/inspect/common.js

@@ -0,0 +1,161 @@
+/**
+ * 门店巡检与考核模块通用工具方法。
+ */
+import querystring from 'querystring'
+
+/** 发送 POST 表单请求,返回 res.data */
+export function post(axios, url, data, config) {
+  return axios.post(url, querystring.stringify(data || {}), config).then(res => res.data)
+}
+
+/** 是否成功(rtnCode === 0) */
+export function ok(res) {
+  return !!(res && res.rtnBean && String(res.rtnBean.rtnCode) === '0')
+}
+
+/** 后端返回的提示信息 */
+export function msg(res) {
+  return (res && res.rtnBean && res.rtnBean.rtnMsg) || ''
+}
+
+/** 分页对象 */
+export function pageData(res) {
+  return (res && res.page) || {}
+}
+
+/** 列表 */
+export function list(res) {
+  return (res && res.list) || []
+}
+
+function pad(n) {
+  return n < 10 ? '0' + n : '' + n
+}
+
+/** 时间格式化:兼容时间戳数字、yyyy-MM-dd HH:mm:ss、yyyy-MM-dd */
+export function fmt(value, withTime) {
+  if (value === null || value === undefined || value === '') return '-'
+  let d = null
+  if (value instanceof Date) {
+    d = value
+  } else if (typeof value === 'number') {
+    d = new Date(value)
+  } else {
+    const s = String(value)
+    const m = s.match(/^(\d{4})-(\d{1,2})-(\d{1,2})(?:[ T](\d{1,2}):(\d{1,2})(?::(\d{1,2}))?)?/)
+    if (m) {
+      const base = m[1] + '-' + pad(+m[2]) + '-' + pad(+m[3])
+      if (withTime && m[4] !== undefined) {
+        return base + ' ' + pad(+m[4]) + ':' + pad(+(m[5] || 0)) + ':' + pad(+(m[6] || 0))
+      }
+      return base
+    }
+    d = new Date(s)
+  }
+  if (!d || isNaN(d.getTime())) return String(value)
+  const base = d.getFullYear() + '-' + pad(d.getMonth() + 1) + '-' + pad(d.getDate())
+  return withTime
+    ? base + ' ' + pad(d.getHours()) + ':' + pad(d.getMinutes()) + ':' + pad(d.getSeconds())
+    : base
+}
+
+/** 剔除空值参数:空字符串/Date 字段绑定到后端某些类型(如 Date)会触发 400,统一不发送空参数 */
+export function cleanParams(obj) {
+  const out = {}
+  Object.keys(obj || {}).forEach(k => {
+    if (k === 'createDate' || k === 'updateDate') return
+    const v = obj[k]
+    if (v === '' || v === null || v === undefined) return
+    out[k] = v
+  })
+  return out
+}
+
+/** 后端实体/搜索Bean中的 Date 字段默认按 "yyyy/MM/dd" 绑定,需将 "yyyy-MM-dd" 转斜杠格式 */
+export function slashDate(value) {
+  if (!value) return value
+  const s = String(value)
+  if (s.indexOf('-') !== -1) {
+    return s.replace(/(\d{4})-(\d{1,2})-(\d{1,2})/, '$1/$2/$3')
+  }
+  return s
+}
+
+/** 数字格式化:去除无意义的小数位 */
+export function fmtNum(value) {
+  if (value === null || value === undefined || value === '') return '0'
+  const n = Number(value)
+  if (isNaN(n)) return String(value)
+  return String(Math.round(n * 100) / 100)
+}
+
+/** 图片地址字符串(逗号/分号分隔)转数组 */
+export function splitPictures(str) {
+  if (!str) return []
+  return String(str).split(/[,;]/).map(s => s.trim()).filter(Boolean)
+}
+
+/** 图片数组转逗号分隔字符串 */
+export function joinPictures(arr) {
+  return (arr || []).filter(Boolean).join(',')
+}
+
+/** 上传图片:file 为 File 对象,返回 Promise<res.data> */
+export function upload(axios, file, dir) {
+  const fd = new FormData()
+  fd.append('file', file)
+  if (dir) fd.append('dir', dir || '/inspect')
+  return axios.post('/inspectManage/uploadPicture', fd, {
+    headers: { 'Content-Type': 'multipart/form-data' }
+  }).then(res => res.data)
+}
+
+/** 从 localStorage 读取当前登录用户 */
+export function currentUser() {
+  let bean = {}
+  try {
+    bean = JSON.parse(localStorage.getItem('userbean') || '{}')
+  } catch (e) { /* ignore */ }
+  return {
+    userId: bean.userId || null,
+    userName: bean.userName || bean.userCode || localStorage.getItem('am_username') || ''
+  }
+}
+
+/* ---------------- 状态/字典文案 ---------------- */
+
+export const PLAN_TYPE = { 1: '常规巡检', 2: '专项巡检', 3: '突击检查' }
+export const CYCLE_TYPE = { 1: '每日', 2: '每周', 3: '每月', 4: '单次' }
+export const PLAN_STATUS = {
+  0: { text: '草稿', type: 'info' },
+  1: { text: '已发布', type: 'success' },
+  2: { text: '已暂停', type: 'warning' },
+  3: { text: '已结束', type: 'danger' }
+}
+export const TASK_STATUS = {
+  1: { text: '待执行', type: 'warning' },
+  2: { text: '执行中', type: 'primary' },
+  3: { text: '已提交', type: 'success' },
+  4: { text: '已取消', type: 'info' }
+}
+export const RECT_STATUS = {
+  1: { text: '待整改', type: 'warning', color: '#E6A23C' },
+  2: { text: '整改中', type: 'primary', color: '#409EFF' },
+  3: { text: '待复检', type: 'primary', color: '#9B59B6' },
+  4: { text: '复检通过', type: 'success', color: '#67C23A' },
+  5: { text: '复检不通过', type: 'danger', color: '#F56C6C' },
+  6: { text: '已超期', type: 'info', color: '#909399' },
+  7: { text: '最终不通过', type: 'danger', color: '#F56C6C' }
+}
+export const ASSESS_LEVEL = {
+  1: { text: '优秀', type: 'success' },
+  2: { text: '良好', type: 'primary' },
+  3: { text: '合格', type: 'warning' },
+  4: { text: '不合格', type: 'danger' }
+}
+export const INDICATOR_TYPE = { 1: '自动采集', 2: '人工巡检' }
+
+export function mapText(map, v) {
+  const item = map[v]
+  return item ? item.text : '-'
+}

+ 33 - 0
src/components/page/admin/inspect/indicator/Index.vue

@@ -0,0 +1,33 @@
+<template>
+  <section class="panel">
+    <div class="toolbar"><el-input v-model="query.indicatorName" placeholder="指标名称" clearable/><el-input v-model="query.indicatorCode" placeholder="指标编码" clearable/><el-select v-model="query.indicatorType" placeholder="指标类型" clearable><el-option label="自动采集" :value="1"/><el-option label="人工巡检" :value="2"/></el-select><el-select v-model="query.status" placeholder="状态" clearable><el-option label="启用" :value="1"/><el-option label="停用" :value="0"/></el-select><el-button type="primary" @click="load">查询</el-button><el-button @click="reset">重置</el-button><span class="grow"></span><el-button type="success" plain @click="checkWeight">权重校验</el-button><el-button type="primary" @click="open()">新增指标</el-button></div>
+    <el-table :data="rows" border stripe><el-table-column prop="sort" label="排序" width="70"/><el-table-column prop="indicatorCode" label="指标编码" width="140"/><el-table-column prop="indicatorName" label="指标名称" min-width="150"/><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="数据来源" min-width="150"/><el-table-column prop="maxScore" label="满分" width="80"/><el-table-column prop="passScore" label="及格分" width="80"/><el-table-column prop="weight" label="权重%" width="90"/><el-table-column label="状态" width="90"><template slot-scope="s"><el-switch v-model="s.row.status" :active-value="1" :inactive-value="0" @change="changeStatus(s.row)"/></template></el-table-column><el-table-column label="操作" width="140"><template slot-scope="s"><el-button type="text" @click="open(s.row)">编辑</el-button><el-button type="text" class="danger" @click="remove(s.row)">删除</el-button></template></el-table-column></el-table>
+    <div class="weight-bar" :class="weightValid ? 'ok' : 'warn'">
+      {{ weightValid ? '✓' : '⚠' }} 启用指标权重合计:<b>{{ weightSum }}</b>%({{ weightValid ? '校验通过' : '需等于100%才可发布' }})
+    </div>
+    <el-dialog title="考核指标配置" :visible.sync="dialog" width="640px"><el-form :model="form" label-width="100px"><el-form-item label="指标编码"><el-input v-model="form.indicatorCode"/></el-form-item><el-form-item label="指标名称"><el-input v-model="form.indicatorName"/></el-form-item><el-form-item label="指标类型"><el-radio-group v-model="form.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="form.dataSource" placeholder="ORDER_COOK_TIME / EVALUATE_GOOD_RATE 等"/></el-form-item><el-form-item label="满分"><el-input-number v-model="form.maxScore" :min="0"/></el-form-item><el-form-item label="及格分"><el-input-number v-model="form.passScore" :min="0"/></el-form-item><el-form-item label="权重"><el-input-number v-model="form.weight" :min="0" :max="100"/></el-form-item><el-form-item label="排序"><el-input-number v-model="form.sort" :min="0"/></el-form-item><el-form-item label="评分规则JSON"><el-input v-model="form.scoreRule" type="textarea" :rows="6" placeholder='{"type":"range","ranges":[...]}'/></el-form-item><el-form-item label="备注"><el-input v-model="form.rmk"/></el-form-item></el-form><span slot="footer"><el-button @click="dialog=false">取消</el-button><el-button type="primary" @click="save">保存</el-button></span></el-dialog>
+  </section>
+</template>
+<script>
+import querystring from 'querystring'
+export default {
+  name:'InspectIndicatorIndex',
+  data(){return{query:{},rows:[],dialog:false,form:{},weightSum:0}},
+  computed:{ weightValid(){return Math.round(this.weightSum)===100} },
+  created(){this.load();this.loadWeight()},
+  methods:{
+    post(u,d){return this.$axios.post(u,querystring.stringify(d||{}))},
+    list(r){return r.data&&r.data.list||[]},
+    load(){return this.post('/inspectManage/indicator/page',Object.assign({pageSize:50,currentPage:1},this.query)).then(r=>{this.rows=this.list(r);this.loadWeight()})},
+    loadWeight(){return this.post('/inspectManage/indicator/checkWeight').then(r=>{this.weightSum=Number((r.data&&r.data.obj)||0)})},
+    reset(){this.query={};this.load()},
+    open(row){this.form=Object.assign({indicatorType:2,maxScore:100,passScore:60,weight:0,sort:0,status:1},row||{});this.dialog=true},
+    save(){const payload=Object.assign({},this.form);delete payload.createDate;delete payload.updateDate;this.post(this.form.indicatorId?'/inspectManage/indicator/update':'/inspectManage/indicator/save',payload).then(()=>{this.dialog=false;this.load();this.$message.success('保存完成')})},
+    changeStatus(row){this.post('/inspectManage/indicator/changeStatus',{id:row.indicatorId,status:row.status}).then(()=>{this.$message.success('状态已更新');this.loadWeight()})},
+    remove(row){this.$confirm('确认删除该指标?','提示').then(()=>this.post('/inspectManage/indicator/delete',{id:row.indicatorId}).then(()=>{this.load();this.$message.success('删除完成')}))},
+    checkWeight(){this.post('/inspectManage/indicator/checkWeight').then(r=>this.$message.info('启用指标权重合计:'+(r.data.obj||0)+'%'))},
+    typeText(v){return v===1?'自动采集':'人工巡检'}
+  }
+}
+</script>
+<style scoped>.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}.weight-bar{margin-top:16px;padding:10px 16px;border-radius:4px;font-size:13px;display:flex;align-items:center;gap:6px}.weight-bar.ok{background:#f6ffed;border:1px solid #b7eb8f;color:#67c23a}.weight-bar.warn{background:#fff7e6;border:1px solid #ffd591;color:#e6a23c}.weight-bar b{font-size:16px}</style>

+ 456 - 0
src/components/page/admin/inspect/plan/Create.vue

@@ -0,0 +1,456 @@
+<template>
+  <el-dialog :title="isEdit ? '编辑巡检计划' : '创建巡检计划'" :visible.sync="dialogVisible" width="860px">
+    <el-steps :active="active" finish-status="success" align-center style="margin-bottom: 22px;">
+      <el-step title="基本信息" />
+      <el-step title="选择指标" />
+      <el-step title="选择门店" />
+      <el-step title="确认提交" />
+    </el-steps>
+
+    <!-- 步骤1:基本信息 -->
+    <div v-show="active === 0" class="step-body">
+      <el-form :model="form" label-width="100px" style="max-width: 560px;">
+        <el-form-item label="计划名称" required>
+          <el-input v-model="form.planName" placeholder="请输入计划名称" />
+        </el-form-item>
+        <el-form-item label="计划类型">
+          <el-radio-group v-model="form.planType">
+            <el-radio :label="1">常规巡检</el-radio>
+            <el-radio :label="2">专项巡检</el-radio>
+            <el-radio :label="3">突击检查</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label="巡检周期">
+          <el-radio-group v-model="form.cycleType">
+            <el-radio :label="1">每日</el-radio>
+            <el-radio :label="2">每周</el-radio>
+            <el-radio :label="3">每月</el-radio>
+            <el-radio :label="4">单次</el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <el-form-item label="日期范围" required>
+          <el-date-picker v-model="form.startDate" type="date" value-format="yyyy-MM-dd" placeholder="开始日期" style="width: 220px;" />
+          <span class="sep">至</span>
+          <el-date-picker v-model="form.endDate" type="date" value-format="yyyy-MM-dd" placeholder="结束日期" style="width: 220px;" />
+        </el-form-item>
+        <el-form-item label="巡检员">
+          <el-select v-model="form.inspectorId" filterable clearable placeholder="选择巡检员" style="width: 100%;" @change="onInspectorChange">
+            <el-option v-for="u in users" :key="u.userId" :label="u.userName" :value="u.userId" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="自动采集">
+          <el-switch v-model="form.autoCollectFlag" :active-value="1" :inactive-value="0" active-text="开启" inactive-text="关闭" />
+        </el-form-item>
+        <el-form-item label="备注">
+          <el-input v-model="form.rmk" type="textarea" :rows="2" placeholder="计划备注" />
+        </el-form-item>
+      </el-form>
+    </div>
+
+    <!-- 步骤2:选择指标 -->
+    <div v-show="active === 1" class="step-body">
+      <div class="weight-bar" :class="{ warn: !weightValid }">
+        已选指标权重合计:<b>{{ weightSum }}</b>%
+        <el-tag size="mini" v-if="weightValid" type="success">权重=100,可发布</el-tag>
+        <el-tag size="mini" v-else type="warning">需等于100%才可发布</el-tag>
+        <span class="grow"></span>
+        <el-checkbox v-model="selectAllIndicators" @change="toggleAllIndicators">全选</el-checkbox>
+      </div>
+      <div class="check-list">
+        <div v-for="item in indicatorPool" :key="item.indicatorId" class="check-row">
+          <el-checkbox v-model="selectedIndicatorIds" :label="item.indicatorId" @change="onIndicatorChange" />
+          <span class="row-main">{{ item.indicatorName }}</span>
+          <el-tag size="mini" :type="item.indicatorType === 1 ? 'primary' : 'warning'" class="row-tag">{{ item.indicatorType === 1 ? '自动' : '人工' }}</el-tag>
+          <span class="row-sub">满分{{ fmtNum(item.maxScore) }} / 及格{{ fmtNum(item.passScore) }}</span>
+          <span class="row-weight">权重 {{ fmtNum(selectedMap[item.indicatorId] ? selectedMap[item.indicatorId].weight : item.weight) }}%</span>
+        </div>
+      </div>
+      <div class="empty-tip" v-if="!indicatorPool.length">暂无启用中的考核指标,请先在“考核指标配置”中新增并启用指标。</div>
+    </div>
+
+    <!-- 步骤3:选择门店 -->
+    <div v-show="active === 2" class="step-body">
+      <div class="store-filter">
+        <el-select v-model="belongtoFilter" placeholder="全部校区" clearable style="width: 180px;">
+          <el-option v-for="b in belongtoOptions" :key="b.value" :label="b.label" :value="b.value" />
+        </el-select>
+        <el-select v-model="provinceFilter" placeholder="全部区域" clearable style="width: 180px;">
+          <el-option v-for="p in provinceOptions" :key="p.value" :label="p.label" :value="p.value" />
+        </el-select>
+        <el-input v-model="storeSearch" placeholder="门店名称搜索" clearable style="width: 200px;" />
+        <span class="grow"></span>
+        <span class="selected-count">已选择 <b>{{ selectedStoreIds.length }}</b> 家门店</span>
+      </div>
+      <div class="check-list">
+        <div v-for="item in filteredStores" :key="item.storeId" class="check-row">
+          <el-checkbox v-model="selectedStoreIds" :label="item.storeId" @change="onStoreChange" />
+          <span class="row-main">{{ item.storeName }}</span>
+          <el-tag size="mini" type="info" class="row-tag" v-if="item.storeBelongtoName">{{ item.storeBelongtoName }}</el-tag>
+          <el-tag size="mini" type="info" class="row-tag" v-if="item.provinceName">{{ item.provinceName }}</el-tag>
+          <el-tag size="mini" type="info" class="row-tag" v-if="item.cityName">{{ item.cityName }}</el-tag>
+        </div>
+      </div>
+      <div class="empty-tip" v-if="!storePool.length">暂无门店数据,请联系管理员确认门店配置。</div>
+    </div>
+
+    <!-- 步骤4:确认预览 -->
+    <div v-show="active === 3" class="step-body">
+      <div class="preview-box">
+        <div class="pv-row"><span class="pv-label">计划名称</span><span>{{ form.planName }}</span></div>
+        <div class="pv-row"><span class="pv-label">计划类型</span><span>{{ planTypeText(form.planType) }}</span></div>
+        <div class="pv-row"><span class="pv-label">巡检周期</span><span>{{ cycleText(form.cycleType) }}</span></div>
+        <div class="pv-row"><span class="pv-label">日期范围</span><span>{{ form.startDate }} ~ {{ form.endDate }}</span></div>
+        <div class="pv-row"><span class="pv-label">巡检员</span><span>{{ form.inspectorName || '-' }}</span></div>
+        <div class="pv-row"><span class="pv-label">自动采集</span><span>{{ form.autoCollectFlag === 1 ? '开启' : '关闭' }}</span></div>
+        <div class="pv-row"><span class="pv-label">备注</span><span>{{ form.rmk || '-' }}</span></div>
+        <div class="pv-row pv-col">
+          <span class="pv-label">关联指标</span>
+          <span>
+            <el-tag size="mini" v-for="it in selectedIndicators" :key="it.indicatorId" class="preview-tag" :type="it.indicatorType === 1 ? 'primary' : 'warning'">
+              {{ it.indicatorName }} ({{ it.indicatorType === 1 ? '自动' : '人工' }}, {{ fmtNum(it.weight) }}%)
+            </el-tag>
+            <span v-if="!selectedIndicators.length" class="muted">未选择指标</span>
+            <span class="weight-hint" :class="{ warn: !weightValid }">权重合计 {{ weightSum }}%</span>
+          </span>
+        </div>
+        <div class="pv-row pv-col">
+          <span class="pv-label">关联门店</span>
+          <span>
+            <el-tag size="mini" v-for="st in selectedStores" :key="st.storeId" class="preview-tag" type="info">{{ st.storeName }}</el-tag>
+            <span v-if="!selectedStores.length" class="muted">未选择门店</span>
+          </span>
+        </div>
+      </div>
+    </div>
+
+    <span slot="footer" class="wizard-footer">
+      <el-button v-if="active > 0" @click="active--">上一步</el-button>
+      <el-button v-if="active < 3" type="primary" @click="next">下一步</el-button>
+      <el-button v-if="active === 3" type="primary" :loading="saving" @click="save">提交</el-button>
+      <el-button @click="dialogVisible = false">取消</el-button>
+    </span>
+  </el-dialog>
+</template>
+
+<script>
+import { post, ok, msg, fmtNum, fmt, slashDate, PLAN_TYPE, CYCLE_TYPE } from '../common'
+
+export default {
+  name: 'InspectPlanCreate',
+  props: {
+    visible: Boolean,
+    planId: [Number, String]
+  },
+  computed: {
+    dialogVisible: {
+      get() { return this.visible },
+      set(v) { this.$emit('update:visible', v) }
+    },
+    isEdit() { return !!this.planId },
+    weightSum() {
+      return this.selectedIndicators.reduce((s, it) => s + (Number(it.weight) || 0), 0)
+    },
+    weightValid() { return Math.round(this.weightSum) === 100 },
+    selectedIndicators() {
+      const list = []
+      this.selectedIndicatorIds.forEach(id => {
+        if (this.selectedMap[id]) list.push(this.selectedMap[id])
+      })
+      return list
+    },
+    selectedStores() {
+      const list = []
+      this.selectedStoreIds.forEach(id => {
+        if (this.storeMap[id]) list.push(this.storeMap[id])
+      })
+      return list
+    },
+    allIndicatorsChecked() {
+      return !!this.indicatorPool.length && this.selectedIndicatorIds.length === this.indicatorPool.length
+    },
+    belongtoOptions() {
+      const map = {}
+      this.storePool.forEach(s => {
+        if (s.storeBelongtoName) map[s.storeBelongtoName] = s.storeBelongto || ''
+      })
+      return Object.keys(map).map(k => ({ label: k, value: map[k] }))
+    },
+    provinceOptions() {
+      const map = {}
+      this.storePool.forEach(s => {
+        if (s.provinceName) map[s.provinceName] = s.province || ''
+      })
+      return Object.keys(map).map(k => ({ label: k, value: map[k] }))
+    },
+    filteredStores() {
+      const kw = (this.storeSearch || '').trim()
+      return this.storePool.filter(s => {
+        if (kw && (s.storeName || '').indexOf(kw) === -1) return false
+        if (this.belongtoFilter && String(s.storeBelongto || '') !== String(this.belongtoFilter)) return false
+        if (this.provinceFilter && String(s.province || '') !== String(this.provinceFilter)) return false
+        return true
+      })
+    }
+  },
+  data() {
+    return {
+      active: 0,
+      saving: false,
+      form: {
+        planId: null, planName: '', planType: 1, cycleType: 2,
+        startDate: '', endDate: '', inspectorId: null, inspectorName: '',
+        autoCollectFlag: 1, rmk: '', storeCount: 0
+      },
+      users: [],
+      indicatorPool: [],
+      selectedIndicatorIds: [],
+      selectedMap: {},
+      selectAllIndicators: false,
+      storePool: [],
+      storeMap: {},
+      selectedStoreIds: [],
+      belongtoFilter: '',
+      provinceFilter: '',
+      storeSearch: ''
+    }
+  },
+  watch: {
+    visible(v) {
+      if (v) this.init()
+    }
+  },
+  methods: {
+    init() {
+      this.active = 0
+      this.resetForm()
+      this.loadUsers()
+      this.loadIndicators()
+      this.loadStores()
+      if (this.planId) this.loadPlan()
+    },
+    resetForm() {
+      this.form = {
+        planId: this.planId || null, planName: '', planType: 1, cycleType: 2,
+        startDate: '', endDate: '', inspectorId: null, inspectorName: '',
+        autoCollectFlag: 1, rmk: '', storeCount: 0
+      }
+      this.selectedIndicatorIds = []
+      this.selectedMap = {}
+      this.selectedStoreIds = []
+      this.storeMap = {}
+      this.belongtoFilter = ''
+      this.provinceFilter = ''
+      this.storeSearch = ''
+    },
+    loadUsers() {
+      post(this.$axios, '/api/usermanage/getUserPage', { pageSize: 200, currentPage: 1 }).then(res => {
+        const list = (res && res.list) || []
+        this.users = list
+        // 编辑时若原巡检员不在列表中,补充一个选项,避免回显为空
+        if (this.form.inspectorId && !list.some(u => String(u.userId) === String(this.form.inspectorId))) {
+          this.users = list.concat([{ userId: this.form.inspectorId, userName: this.form.inspectorName || '未知巡检员' }])
+        }
+      })
+    },
+    loadIndicators() {
+      post(this.$axios, '/inspectManage/indicator/page', { pageSize: 200, currentPage: 1, status: 1 }).then(res => {
+        this.indicatorPool = (res && res.list) || []
+      })
+    },
+    loadStores() {
+      post(this.$axios, '/storeManage/getStoreList', {}).then(res => {
+        this.storePool = (res && res.list) || []
+        this.storeMap = {}
+        this.storePool.forEach(s => { this.storeMap[s.storeId] = s })
+      })
+    },
+    loadPlan() {
+      post(this.$axios, '/inspectManage/plan/detail', { id: this.planId }).then(res => {
+        if (res && res.obj) {
+          const p = res.obj
+          this.form = {
+            planId: p.planId, planName: p.planName, planType: p.planType,
+            cycleType: p.cycleType,
+            startDate: fmt(p.startDate), endDate: fmt(p.endDate),
+            inspectorId: p.inspectorId, inspectorName: p.inspectorName,
+            autoCollectFlag: p.autoCollectFlag, rmk: p.rmk, storeCount: p.storeCount
+          }
+        }
+      })
+      // 关联指标回显(使用计划内快照权重)
+      post(this.$axios, '/inspectManage/plan/indicators', { planId: this.planId }).then(res => {
+        const list = (res && res.list) || []
+        this.selectedMap = {}
+        list.forEach(it => {
+          this.selectedMap[it.indicatorId] = {
+            indicatorId: it.indicatorId, indicatorName: it.indicatorName, indicatorType: it.indicatorType,
+            maxScore: it.maxScore, passScore: it.passScore, weight: it.weight, scoreRule: it.scoreRule, sort: it.sort
+          }
+        })
+        this.selectedIndicatorIds = list.map(it => it.indicatorId)
+      })
+      // 关联门店回显
+      post(this.$axios, '/inspectManage/plan/stores', { planId: this.planId }).then(res => {
+        const list = (res && res.list) || []
+        this.storeMap = {}
+        this.selectedStoreIds = []
+        list.forEach(st => {
+          this.selectedStoreIds.push(st.storeId)
+          this.storeMap[st.storeId] = { storeId: st.storeId, storeName: st.storeName, storeBelongto: st.storeBelongto, province: st.province, city: st.city }
+        })
+      })
+    },
+    onInspectorChange(v) {
+      const u = this.users.find(x => String(x.userId) === String(v))
+      this.form.inspectorName = u ? u.userName : ''
+    },
+    onIndicatorChange() {
+      // 根据勾选 id 同步指标快照
+      const poolMap = {}
+      this.indicatorPool.forEach(it => { poolMap[it.indicatorId] = it })
+      this.selectedIndicatorIds.forEach(id => {
+        if (!this.selectedMap[id] && poolMap[id]) {
+          const it = poolMap[id]
+          this.selectedMap[id] = {
+            indicatorId: it.indicatorId, indicatorName: it.indicatorName, indicatorType: it.indicatorType,
+            maxScore: it.maxScore, passScore: it.passScore, weight: it.weight, scoreRule: it.scoreRule, sort: it.sort
+          }
+        }
+      })
+      Object.keys(this.selectedMap).forEach(id => {
+        if (this.selectedIndicatorIds.indexOf(Number(id)) === -1 && this.selectedIndicatorIds.indexOf(String(id)) === -1) {
+          delete this.selectedMap[id]
+        }
+      })
+      this.selectAllIndicators = this.allIndicatorsChecked
+    },
+    toggleAllIndicators(checked) {
+      if (checked) {
+        this.selectedIndicatorIds = this.indicatorPool.map(it => it.indicatorId)
+      } else {
+        this.selectedIndicatorIds = []
+      }
+      this.onIndicatorChange()
+    },
+    onStoreChange() { /* 选中值直接绑定 selectedStoreIds,无需额外处理 */ },
+    next() {
+      if (this.active === 0) {
+        if (!this.form.planName || !this.form.planName.trim()) return this.$message.warning('请输入计划名称')
+        if (!this.form.startDate || !this.form.endDate) return this.$message.warning('请选择日期范围')
+        if (this.form.endDate <= this.form.startDate) return this.$message.warning('结束日期必须晚于开始日期')
+      } else if (this.active === 1) {
+        if (!this.selectedIndicatorIds.length) return this.$message.warning('请至少选择一个考核指标')
+        if (!this.weightValid) this.$message.warning('当前权重合计 ' + this.weightSum + '%,需等于100%才可发布,可先保存草稿')
+      } else if (this.active === 2) {
+        if (!this.selectedStoreIds.length) return this.$message.warning('请至少选择一家门店')
+      }
+      this.active++
+    },
+    save() {
+      if (!this.form.planName || !this.form.planName.trim()) return this.$message.warning('请输入计划名称')
+      if (!this.form.startDate || !this.form.endDate) return this.$message.warning('请选择日期范围')
+      if (!this.selectedIndicatorIds.length) return this.$message.warning('请至少选择一个考核指标')
+      if (!this.selectedStoreIds.length) return this.$message.warning('请至少选择一家门店')
+      if (!this.weightValid) {
+        return this.$confirm('权重合计 ' + this.weightSum + '%,不等于100%将无法发布。确认仍要保存草稿?', '提示')
+          .then(() => this.doSave()).catch(() => {})
+      }
+      return this.doSave()
+    },
+    doSave() {
+      this.saving = true
+      this.form.storeCount = this.selectedStoreIds.length
+      const planPayload = {
+        planId: this.form.planId,
+        planName: this.form.planName,
+        planType: this.form.planType,
+        cycleType: this.form.cycleType,
+        startDate: slashDate(this.form.startDate),
+        endDate: slashDate(this.form.endDate),
+        inspectorId: this.form.inspectorId,
+        inspectorName: this.form.inspectorName,
+        autoCollectFlag: this.form.autoCollectFlag,
+        rmk: this.form.rmk,
+        storeCount: this.form.storeCount,
+        status: 0
+      }
+      const planAction = this.isEdit ? '/inspectManage/plan/update' : '/inspectManage/plan/save'
+      post(this.$axios, planAction, planPayload).then(res => {
+        if (!ok(res)) throw new Error(msg(res) || '计划保存失败')
+        if (this.isEdit) {
+          return this.saveAssociations(this.planId)
+        }
+        return this.resolveNewPlanId().then(planId => this.saveAssociations(planId))
+      }).then(() => {
+        this.$emit('saved')
+        this.$message.success('保存完成')
+      }).catch(err => {
+        this.$message.error(err && err.message ? err.message : '保存失败')
+      }).finally(() => { this.saving = false })
+    },
+    resolveNewPlanId() {
+      return post(this.$axios, '/inspectManage/plan/page', { planName: this.form.planName, pageSize: 1, currentPage: 1 }).then(res => {
+        const rows = (res && res.list) || []
+        if (!rows.length) throw new Error('计划保存成功但未能获取计划ID')
+        return rows[0].planId
+      })
+    },
+    saveAssociations(planId) {
+      // 编辑时先清理旧的关联
+      const cleanUp = this.isEdit
+        ? Promise.all([
+            post(this.$axios, '/inspectManage/plan/indicators', { planId }).then(res => {
+              const list = (res && res.list) || []
+              return Promise.all(list.map(it => post(this.$axios, '/inspectManage/planIndicator/delete', { id: it.planIndicatorId })))
+            }),
+            post(this.$axios, '/inspectManage/plan/stores', { planId }).then(res => {
+              const list = (res && res.list) || []
+              return Promise.all(list.map(st => post(this.$axios, '/inspectManage/planStore/delete', { id: st.planStoreId })))
+            })
+          ])
+        : Promise.resolve([])
+      return cleanUp.then(() => {
+        const indReq = this.selectedIndicators.map(it => post(this.$axios, '/inspectManage/planIndicator/save', Object.assign({}, it, { planId, status: 1 })))
+        const storeReq = this.selectedStores.map(st => post(this.$axios, '/inspectManage/planStore/save', Object.assign({}, st, { planId, status: 1 })))
+        return Promise.all(indReq.concat(storeReq)).then(results => {
+          const failed = results.filter(r => !ok(r))
+          if (failed.length) throw new Error('部分关联保存失败')
+        })
+      })
+    },
+    planTypeText(v) { return PLAN_TYPE[v] || '-' },
+    cycleText(v) { return CYCLE_TYPE[v] || '-' },
+    fmtNum
+  }
+}
+</script>
+
+<style scoped>
+.step-body { min-height: 260px; max-height: 440px; overflow-y: auto; }
+.sep { margin: 0 8px; color: #909399; }
+.weight-bar { display: flex; align-items: center; gap: 10px; background: #f5f7fa; border-radius: 4px; padding: 8px 12px; margin-bottom: 12px; }
+.weight-bar.warn { background: #fdf6ec; }
+.weight-bar b { color: #e6a23c; }
+.grow { flex: 1; }
+.check-list { border: 1px solid #ebeef5; border-radius: 4px; max-height: 300px; overflow-y: auto; }
+.check-row { display: flex; align-items: center; gap: 10px; padding: 8px 14px; border-bottom: 1px solid #f0f2f5; }
+.check-row:last-child { border-bottom: none; }
+.row-main { flex: 1; }
+.row-tag { margin-right: 4px; }
+.row-sub { color: #909399; font-size: 12px; width: 140px; }
+.row-weight { color: #606266; font-size: 12px; min-width: 90px; text-align: right; }
+.store-filter { display: flex; gap: 10px; align-items: center; margin-bottom: 12px; flex-wrap: wrap; }
+.selected-count b { color: #409eff; }
+.empty-tip { color: #909399; padding: 18px; text-align: center; }
+.preview-tag { margin: 2px 6px 2px 0; }
+.weight-hint { margin-left: 8px; color: #67c23a; }
+.weight-hint.warn { color: #e6a23c; }
+.muted { color: #909399; }
+.wizard-footer { display: block; text-align: right; }
+.preview-box { border: 1px solid #ebeef5; border-radius: 4px; }
+.pv-row { display: flex; padding: 10px 14px; border-bottom: 1px solid #f0f2f5; line-height: 1.6; }
+.pv-row:last-child { border-bottom: none; }
+.pv-label { width: 90px; color: #909399; flex-shrink: 0; }
+.pv-col { flex-direction: column; gap: 6px; }
+.pv-col > .pv-label { margin-bottom: 4px; }
+</style>

+ 127 - 0
src/components/page/admin/inspect/plan/Detail.vue

@@ -0,0 +1,127 @@
+<template>
+  <el-dialog title="计划详情" :visible.sync="dialogVisible" width="880px" @open="load">
+    <div class="detail-wrap" v-loading="loading">
+      <div class="block">
+        <h4>基本信息</h4>
+        <div class="info-grid" v-if="plan">
+          <div class="info-item"><label>计划名称</label><span>{{ plan.planName || '-' }}</span></div>
+          <div class="info-item"><label>计划类型</label><span>{{ planTypeText(plan.planType) }}</span></div>
+          <div class="info-item"><label>巡检周期</label><span>{{ cycleText(plan.cycleType) }}</span></div>
+          <div class="info-item"><label>日期范围</label><span>{{ fmt(plan.startDate) }} ~ {{ fmt(plan.endDate) }}</span></div>
+          <div class="info-item"><label>下次执行</label><span>{{ fmt(plan.nextExecuteDate) }}</span></div>
+          <div class="info-item"><label>巡检员</label><span>{{ plan.inspectorName || '-' }}</span></div>
+          <div class="info-item"><label>门店数</label><span>{{ plan.storeCount || 0 }}</span></div>
+          <div class="info-item"><label>状态</label><span><el-tag size="mini" :type="planStatus(plan.status).type">{{ planStatus(plan.status).text }}</el-tag></span></div>
+          <div class="info-item"><label>自动采集</label><span>{{ plan.autoCollectFlag === 1 ? '开启' : '关闭' }}</span></div>
+          <div class="info-item full"><label>备注</label><span>{{ plan.rmk || '-' }}</span></div>
+        </div>
+      </div>
+
+      <div class="block">
+        <h4>关联指标({{ indicators.length }})</h4>
+        <el-table :data="indicators" border size="mini" max-height="260">
+          <el-table-column prop="indicatorName" label="指标名称" min-width="130" />
+          <el-table-column label="类型" width="80">
+            <template slot-scope="s"><el-tag size="mini" :type="s.row.indicatorType === 1 ? 'primary' : 'warning'">{{ s.row.indicatorType === 1 ? '自动' : '人工' }}</el-tag></template>
+          </el-table-column>
+          <el-table-column prop="maxScore" label="满分" width="70" align="center" />
+          <el-table-column prop="passScore" label="及格分" width="70" align="center" />
+          <el-table-column prop="weight" label="权重%" width="80" align="center" />
+        </el-table>
+      </div>
+
+      <div class="block">
+        <h4>关联门店({{ stores.length }})</h4>
+        <el-table :data="stores" border size="mini" max-height="260">
+          <el-table-column prop="storeName" label="门店名称" min-width="160" />
+          <el-table-column prop="storeBelongto" label="归属校区" width="100" />
+          <el-table-column prop="province" label="省" width="80" />
+          <el-table-column prop="city" label="市" width="80" />
+        </el-table>
+      </div>
+
+      <div class="block">
+        <div class="block-head">
+          <h4>已生成任务({{ tasks.length }})</h4>
+          <el-button type="primary" size="mini" @click="$emit('generate', plan && plan.planId)">生成任务</el-button>
+        </div>
+        <el-table :data="tasks" border size="mini" max-height="280">
+          <el-table-column prop="taskId" label="任务ID" width="80" />
+          <el-table-column prop="storeName" label="门店" min-width="140" />
+          <el-table-column label="执行日期" width="110">
+            <template slot-scope="s">{{ fmt(s.row.taskDate) }}</template>
+          </el-table-column>
+          <el-table-column label="总分" width="100">
+            <template slot-scope="s">{{ fmtNum(s.row.totalScore) }}/{{ fmtNum(s.row.maxScore) }}</template>
+          </el-table-column>
+          <el-table-column label="结果" width="80">
+            <template slot-scope="s">
+              <el-tag v-if="s.row.status === 3" size="mini" :type="s.row.passFlag === 1 ? 'success' : 'danger'">{{ s.row.passFlag === 1 ? '及格' : '不及格' }}</el-tag>
+              <span v-else>-</span>
+            </template>
+          </el-table-column>
+          <el-table-column label="状态" width="90">
+            <template slot-scope="s"><el-tag size="mini" :type="taskStatus(s.row.status).type">{{ taskStatus(s.row.status).text }}</el-tag></template>
+          </el-table-column>
+        </el-table>
+        <div class="empty" v-if="!tasks.length">该计划暂未生成任务,可点击“生成任务”按执行日期创建。</div>
+      </div>
+    </div>
+  </el-dialog>
+</template>
+
+<script>
+import { post, fmt, fmtNum, PLAN_TYPE, CYCLE_TYPE, PLAN_STATUS, TASK_STATUS } from '../common'
+
+export default {
+  name: 'InspectPlanDetail',
+  props: {
+    visible: Boolean,
+    planId: [Number, String]
+  },
+  computed: {
+    dialogVisible: {
+      get() { return this.visible },
+      set(v) { this.$emit('update:visible', v) }
+    }
+  },
+  data() {
+    return { loading: false, plan: null, indicators: [], stores: [], tasks: [] }
+  },
+  methods: {
+    load() {
+      if (!this.planId) return
+      this.loading = true
+      Promise.all([
+        post(this.$axios, '/inspectManage/plan/detail', { id: this.planId }),
+        post(this.$axios, '/inspectManage/plan/indicators', { planId: this.planId }),
+        post(this.$axios, '/inspectManage/plan/stores', { planId: this.planId }),
+        post(this.$axios, '/inspectManage/task/page', { planId: this.planId, pageSize: 50, currentPage: 1 })
+      ]).then(([r1, r2, r3, r4]) => {
+        this.plan = (r1 && r1.obj) || null
+        this.indicators = (r2 && r2.list) || []
+        this.stores = (r3 && r3.list) || []
+        this.tasks = (r4 && r4.list) || []
+      }).finally(() => { this.loading = false })
+    },
+    planStatus(v) { return PLAN_STATUS[v] || { text: '-', type: 'info' } },
+    taskStatus(v) { return TASK_STATUS[v] || { text: '-', type: 'info' } },
+    planTypeText(v) { return PLAN_TYPE[v] || '-' },
+    cycleText(v) { return CYCLE_TYPE[v] || '-' },
+    fmt,
+    fmtNum
+  }
+}
+</script>
+
+<style scoped>
+.detail-wrap { max-height: 560px; overflow-y: auto; }
+.block { margin-bottom: 18px; }
+.block h4 { margin: 0 0 10px; color: #303133; }
+.info-grid { display: flex; flex-wrap: wrap; background: #fafbfc; border: 1px solid #ebeef5; border-radius: 4px; padding: 6px 0; }
+.info-item { width: 33.33%; display: flex; padding: 8px 14px; box-sizing: border-box; }
+.info-item.full { width: 100%; }
+.info-item label { width: 74px; color: #909399; flex-shrink: 0; }
+.block-head { display: flex; justify-content: space-between; align-items: center; }
+.empty { color: #909399; padding: 12px 0; }
+</style>

+ 217 - 0
src/components/page/admin/inspect/plan/Index.vue

@@ -0,0 +1,217 @@
+<template>
+  <section class="panel">
+    <div class="toolbar">
+      <el-input v-model="query.planName" placeholder="计划名称" clearable />
+      <el-select v-model="query.planType" placeholder="计划类型" clearable>
+        <el-option label="常规巡检" :value="1" />
+        <el-option label="专项巡检" :value="2" />
+        <el-option label="突击检查" :value="3" />
+      </el-select>
+      <el-select v-model="query.cycleType" 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-select v-model="query.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="load()">查询</el-button>
+      <el-button @click="reset">重置</el-button>
+      <span class="grow"></span>
+      <el-button type="primary" @click="openCreate()">新增计划</el-button>
+    </div>
+
+    <el-table :data="rows" border stripe v-loading="loading">
+      <el-table-column prop="planId" label="ID" width="70" />
+      <el-table-column prop="planName" label="计划名称" min-width="150" />
+      <el-table-column label="类型" width="90">
+        <template slot-scope="s"><el-tag size="mini" type="info">{{planTypeText(s.row.planType)}}</el-tag></template>
+      </el-table-column>
+      <el-table-column label="周期" width="80">
+        <template slot-scope="s">{{cycleText(s.row.cycleType)}}</template>
+      </el-table-column>
+      <el-table-column label="执行日期" width="180">
+        <template slot-scope="s">{{fmt(s.row.startDate)}} ~ {{fmt(s.row.endDate)}}</template>
+      </el-table-column>
+      <el-table-column label="下次执行" width="105">
+        <template slot-scope="s">{{fmt(s.row.nextExecuteDate)}}</template>
+      </el-table-column>
+      <el-table-column prop="inspectorName" label="巡检员" width="90">
+        <template slot-scope="s">{{s.row.inspectorName || '-'}}</template>
+      </el-table-column>
+      <el-table-column prop="storeCount" label="门店数" width="75" align="center">
+        <template slot-scope="s">{{s.row.storeCount || 0}}</template>
+      </el-table-column>
+      <el-table-column label="自动采集" width="85" align="center">
+        <template slot-scope="s">
+          <el-tag size="mini" :type="s.row.autoCollectFlag === 1 ? 'success' : 'info'">{{s.row.autoCollectFlag === 1 ? '开启' : '关闭'}}</el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="状态" width="90">
+        <template slot-scope="s"><el-tag size="mini" :type="planStatus(s.row.status).type">{{planStatus(s.row.status).text}}</el-tag></template>
+      </el-table-column>
+      <el-table-column label="操作" width="300" fixed="right">
+        <template slot-scope="s">
+          <el-button type="text" @click="openDetail(s.row)">详情</el-button>
+          <el-button type="text" v-if="s.row.status === 0" @click="openCreate(s.row)">编辑</el-button>
+          <el-button type="text" v-if="s.row.status === 0" @click="publish(s.row)">发布</el-button>
+          <el-button type="text" v-if="s.row.status === 1" @click="pause(s.row)">暂停</el-button>
+          <el-button type="text" v-if="s.row.status === 1 || s.row.status === 2" @click="finish(s.row)">结束</el-button>
+          <el-button type="text" v-if="s.row.status === 1 || s.row.status === 2" @click="openGenerate(s.row)">生成任务</el-button>
+          <el-button type="text" class="danger" v-if="s.row.status === 0" @click="remove(s.row)">删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <div class="pager">
+      <el-pagination
+        background
+        layout="total, sizes, prev, pager, next, jumper"
+        :total="total"
+        :page-size="query.pageSize"
+        :current-page="query.currentPage"
+        @size-change="n => { query.pageSize = n; load() }"
+        @current-change="n => { query.currentPage = n; load() }"
+      />
+    </div>
+
+    <plan-create :visible.sync="createVisible" :plan-id="editId" @saved="onSaved" />
+    <plan-detail :visible.sync="detailVisible" :plan-id="detailId" @generate="openGenerateFromDetail" />
+
+    <el-dialog title="生成巡检任务" :visible.sync="generateVisible" width="420px">
+      <el-form label-width="100px">
+        <el-form-item label="执行日期">
+          <el-date-picker v-model="generateDate" type="date" value-format="yyyy-MM-dd" placeholder="选择执行日期" style="width: 100%;" />
+        </el-form-item>
+      </el-form>
+      <span slot="footer">
+        <el-button @click="generateVisible = false">取消</el-button>
+        <el-button type="primary" @click="generateTask">确定生成</el-button>
+      </span>
+    </el-dialog>
+  </section>
+</template>
+
+<script>
+import { post, ok, msg, pageData, fmt, cleanParams, PLAN_TYPE, CYCLE_TYPE, PLAN_STATUS } from '../common'
+import PlanCreate from './Create.vue'
+import PlanDetail from './Detail.vue'
+
+export default {
+  name: 'InspectPlanIndex',
+  components: { PlanCreate, PlanDetail },
+  data() {
+    return {
+      query: { planName: '', planType: '', cycleType: '', status: '', pageSize: 10, currentPage: 1 },
+      rows: [],
+      total: 0,
+      loading: false,
+      createVisible: false,
+      editId: null,
+      detailVisible: false,
+      detailId: null,
+      generateVisible: false,
+      generatePlan: null,
+      generateDate: ''
+    }
+  },
+  created() { this.load() },
+  methods: {
+    load() {
+      this.loading = true
+      post(this.$axios, '/inspectManage/plan/page', cleanParams(this.query)).then(res => {
+        this.rows = (res && res.list) || []
+        this.total = pageData(res).resultCount || this.rows.length
+      }).finally(() => { this.loading = false })
+    },
+    reset() {
+      this.query = { planName: '', planType: '', cycleType: '', status: '', pageSize: 10, currentPage: 1 }
+      this.load()
+    },
+    openCreate(row) {
+      this.editId = row ? row.planId : null
+      this.createVisible = true
+    },
+    onSaved() {
+      this.createVisible = false
+      this.load()
+      this.$message.success('保存完成')
+    },
+    openDetail(row) {
+      this.detailId = row.planId
+      this.detailVisible = true
+    },
+    openGenerateFromDetail(planId) {
+      const row = this.rows.find(r => String(r.planId) === String(planId)) || { planId }
+      this.detailVisible = false
+      this.openGenerate(row)
+    },
+    publish(row) {
+      this.$confirm('发布后不可编辑,确认发布该计划?', '提示').then(() => {
+        post(this.$axios, '/inspectManage/plan/publish', { id: row.planId }).then(res => {
+          this.$message.success(ok(res) ? '发布完成' : (msg(res) || '发布失败'))
+          if (ok(res)) this.load()
+        })
+      }).catch(() => {})
+    },
+    pause(row) {
+      this.$confirm('确认暂停该计划?暂停后不再生成新任务。', '提示').then(() => {
+        post(this.$axios, '/inspectManage/plan/pause', { id: row.planId }).then(res => {
+          this.$message.success(ok(res) ? '暂停完成' : (msg(res) || '操作失败'))
+          if (ok(res)) this.load()
+        })
+      }).catch(() => {})
+    },
+    finish(row) {
+      this.$confirm('确认结束该计划?结束后不再生成新任务。', '提示').then(() => {
+        post(this.$axios, '/inspectManage/plan/finish', { id: row.planId }).then(res => {
+          this.$message.success(ok(res) ? '结束完成' : (msg(res) || '操作失败'))
+          if (ok(res)) this.load()
+        })
+      }).catch(() => {})
+    },
+    remove(row) {
+      this.$confirm('仅草稿计划可删除,确认删除?', '提示').then(() => {
+        post(this.$axios, '/inspectManage/plan/delete', { id: row.planId }).then(res => {
+          if (ok(res)) { this.load(); this.$message.success('删除完成') } else { this.$message.error(msg(res) || '删除失败') }
+        })
+      }).catch(() => {})
+    },
+    openGenerate(row) {
+      this.generatePlan = row
+      this.generateDate = this.$moment().format('YYYY-MM-DD')
+      this.generateVisible = true
+    },
+    generateTask() {
+      if (!this.generateDate) return this.$message.warning('请选择执行日期')
+      post(this.$axios, '/inspectManage/task/generate', { planId: this.generatePlan.planId, taskDate: this.generateDate }).then(res => {
+        if (ok(res)) {
+          const count = (res && res.obj) || 0
+          this.generateVisible = false
+          this.$message.success('生成完成,共生成 ' + count + ' 条任务')
+          this.load()
+        } else {
+          this.$message.error(msg(res) || '生成失败')
+        }
+      })
+    },
+    planStatus(v) { return PLAN_STATUS[v] || { text: '-', type: 'info' } },
+    planTypeText(v) { return PLAN_TYPE[v] || '-' },
+    cycleText(v) { return CYCLE_TYPE[v] || '-' },
+    fmt
+  }
+}
+</script>
+
+<style scoped>
+.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: 160px; }
+.grow { flex: 1; }
+.danger { color: #f56c6c; }
+.pager { margin-top: 16px; text-align: right; }
+</style>

+ 147 - 0
src/components/page/admin/inspect/rectification/Detail.vue

@@ -0,0 +1,147 @@
+<template>
+  <el-dialog title="整改详情" :visible.sync="dialogVisible" width="760px" @open="load" :close-on-click-modal="false">
+    <div class="detail-wrap" v-loading="loading">
+      <div class="head" v-if="rounds.length">
+        <div class="head-main">{{ rounds[0].storeName }} · {{ rounds[0].indicatorName }}</div>
+        <div class="head-meta">
+          <span>当前轮次:第 {{ rounds[rounds.length - 1].rectRound || 1 }} 轮</span>
+          <span v-if="rounds[0].taskId">任务ID:{{ rounds[0].taskId }}</span>
+        </div>
+      </div>
+
+      <el-timeline v-if="rounds.length">
+        <el-timeline-item v-for="r in rounds" :key="r.rectId" :timestamp="'第 ' + (r.rectRound || 1) + ' 轮整改'" placement="top" :color="rectColor(r.status)">
+          <div class="round-card">
+            <div class="round-head">
+              <span class="round-title">指标:{{ r.indicatorName }}</span>
+              <el-tag size="mini" :style="rectStyle(r.status)">{{ rectText(r.status) }}</el-tag>
+              <el-button v-if="r.status === 3" type="primary" size="mini" @click="$emit('recheck', r)">复检</el-button>
+            </div>
+
+            <!-- 下发通知 -->
+            <div class="node">
+              <div class="node-title"><i class="el-icon-message"></i> 下发整改通知<span class="node-time">{{ fmt(r.createDate, true) }}</span></div>
+              <div class="node-content">
+                <div v-if="r.noticeContent">{{ r.noticeContent }}</div>
+                <div class="kv"><span>巡检得分:{{ fmtNum(r.inspectScore) }} / 及格分:{{ fmtNum(r.passScore) }}</span></div>
+                <div class="kv"><span>截止日期:{{ fmt(r.deadlineDate, true) }}</span></div>
+                <div class="kv" v-if="r.urgeCount"><span>已催办 {{ r.urgeCount }} 次(最近 {{ fmt(r.lastUrgeDate, true) }})</span></div>
+              </div>
+            </div>
+
+            <!-- 门店反馈 -->
+            <div class="node" :class="{ empty: !r.feedbackDate }">
+              <div class="node-title"><i class="el-icon-shop"></i> 门店提交整改反馈<span class="node-time">{{ fmt(r.feedbackDate, true) }}</span></div>
+              <div class="node-content" v-if="r.feedbackDate">
+                <div v-if="r.feedbackContent">{{ r.feedbackContent }}</div>
+                <div class="pic-row" v-if="pictures(r.feedbackPictures).length">
+                  <el-image
+                    v-for="(p, i) in pictures(r.feedbackPictures)"
+                    :key="i"
+                    :src="p"
+                    fit="cover"
+                    :preview-src-list="pictures(r.feedbackPictures)"
+                    class="thumb" />
+                </div>
+              </div>
+              <div class="muted" v-else>门店尚未提交反馈</div>
+            </div>
+
+            <!-- 复检结果 -->
+            <div class="node" :class="{ empty: !r.recheckDate }">
+              <div class="node-title"><i class="el-icon-search"></i> 巡检员复检<span class="node-time">{{ fmt(r.recheckDate, true) }}</span></div>
+              <div class="node-content" v-if="r.recheckDate">
+                <div class="kv">
+                  <span>复检得分:{{ fmtNum(r.recheckScore) }}</span>
+                  <el-tag size="mini" :type="r.recheckResult === 1 ? 'success' : 'danger'" class="kv-tag">{{ r.recheckResult === 1 ? '复检通过' : '复检不通过' }}</el-tag>
+                  <span v-if="r.recheckUserName" class="kv-user">复检人:{{ r.recheckUserName }}</span>
+                </div>
+                <div v-if="r.recheckRmk">{{ r.recheckRmk }}</div>
+                <div class="pic-row" v-if="pictures(r.recheckPictures).length">
+                  <el-image
+                    v-for="(p, i) in pictures(r.recheckPictures)"
+                    :key="i"
+                    :src="p"
+                    fit="cover"
+                    :preview-src-list="pictures(r.recheckPictures)"
+                    class="thumb" />
+                </div>
+              </div>
+              <div class="muted" v-else>尚未复检</div>
+            </div>
+          </div>
+        </el-timeline-item>
+      </el-timeline>
+
+      <div class="empty" v-else>未找到整改记录</div>
+    </div>
+    <span slot="footer">
+      <el-button @click="dialogVisible = false">关闭</el-button>
+    </span>
+  </el-dialog>
+</template>
+
+<script>
+import { post, fmt, fmtNum, splitPictures, RECT_STATUS } from '../common'
+
+export default {
+  name: 'InspectRectificationDetail',
+  props: {
+    visible: Boolean,
+    row: Object
+  },
+  computed: {
+    dialogVisible: {
+      get() { return this.visible },
+      set(v) { this.$emit('update:visible', v) }
+    }
+  },
+  data() {
+    return { loading: false, rounds: [] }
+  },
+  methods: {
+    load() {
+      if (!this.row) return
+      this.loading = true
+      // 同任务同指标的多轮整改:通过 taskItemId 查询全部轮次
+      const q = this.row.taskItemId
+        ? { taskItemId: this.row.taskItemId, pageSize: 50, currentPage: 1 }
+        : { taskId: this.row.taskId, indicatorId: this.row.indicatorId, pageSize: 50, currentPage: 1 }
+      post(this.$axios, '/inspectManage/rectification/page', q).then(res => {
+        const list = (res && res.list) || []
+        this.rounds = list.length ? list.sort((a, b) => (a.rectRound || 1) - (b.rectRound || 1)) : [this.row]
+      }).finally(() => { this.loading = false })
+    },
+    pictures(str) { return splitPictures(str) },
+    rectText(v) { return (RECT_STATUS[v] || {}).text || '-' },
+    rectStyle(v) {
+      const st = RECT_STATUS[v]
+      return { background: st.color, borderColor: st.color, color: '#fff' }
+    },
+    rectColor(v) { return (RECT_STATUS[v] || {}).color || '#909399' },
+    fmt,
+    fmtNum
+  }
+}
+</script>
+
+<style scoped>
+.detail-wrap { max-height: 560px; overflow-y: auto; }
+.head { background: #f5f7fa; border-radius: 4px; padding: 12px 16px; margin-bottom: 16px; }
+.head-main { font-size: 15px; font-weight: 600; margin-bottom: 6px; }
+.head-meta { display: flex; gap: 18px; color: #606266; font-size: 13px; }
+.round-card { border: 1px solid #ebeef5; border-radius: 6px; padding: 12px 14px; }
+.round-head { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; }
+.round-title { font-weight: 600; flex: 1; }
+.node { border-left: 3px solid #ebeef5; padding: 4px 0 12px 14px; margin-left: 4px; }
+.node.empty { border-left-color: #f0f2f5; }
+.node-title { color: #303133; font-weight: 600; margin-bottom: 6px; }
+.node-time { color: #909399; font-weight: 400; font-size: 12px; margin-left: 10px; }
+.node-content { color: #606266; font-size: 13px; line-height: 1.7; }
+.kv { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
+.kv-user { color: #909399; }
+.pic-row { display: flex; gap: 8px; margin-top: 8px; }
+.thumb { width: 56px; height: 56px; border-radius: 4px; }
+.muted { color: #909399; font-size: 12px; }
+.empty { color: #909399; padding: 30px; text-align: center; }
+</style>

+ 244 - 0
src/components/page/admin/inspect/rectification/Index.vue

@@ -0,0 +1,244 @@
+<template>
+  <section class="panel">
+    <div class="toolbar">
+      <el-select v-model="query.storeId" placeholder="门店" clearable filterable style="width: 180px;">
+        <el-option v-for="s in stores" :key="s.storeId" :label="s.storeName" :value="s.storeId" />
+      </el-select>
+      <el-select v-model="query.indicatorId" placeholder="指标" clearable filterable style="width: 180px;">
+        <el-option v-for="i in indicators" :key="i.indicatorId" :label="i.indicatorName" :value="i.indicatorId" />
+      </el-select>
+      <el-select v-model="query.status" placeholder="整改状态" clearable>
+        <el-option label="待整改" :value="1" />
+        <el-option label="整改中" :value="2" />
+        <el-option label="待复检" :value="3" />
+        <el-option label="复检通过" :value="4" />
+        <el-option label="复检不通过" :value="5" />
+        <el-option label="已超期" :value="6" />
+        <el-option label="最终不通过" :value="7" />
+      </el-select>
+      <el-date-picker v-model="deadlineRange" type="daterange" value-format="yyyy-MM-dd" start-placeholder="截止开始" end-placeholder="截止结束" style="width: 240px;" />
+      <el-button type="primary" @click="load()">查询</el-button>
+      <el-button @click="reset">重置</el-button>
+      <span class="grow"></span>
+      <el-button type="warning" plain :disabled="!selected.length" @click="batchVisible = true">批量设置截止日期({{ selected.length }})</el-button>
+      <el-button type="primary" plain @click="loadAll()">刷新</el-button>
+    </div>
+
+    <!-- 整改统计 -->
+    <div class="stat-cards">
+      <div class="stat-card" v-for="c in statItems" :key="c.label">
+        <span>{{ c.label }}</span>
+        <b :style="{ color: c.color }">{{ c.value }}</b>
+        <em>条</em>
+      </div>
+    </div>
+
+    <el-table :data="rows" border stripe v-loading="loading" @selection-change="onSelect">
+      <el-table-column type="selection" width="45" />
+      <el-table-column prop="rectId" label="ID" width="75" />
+      <el-table-column prop="storeName" label="门店" min-width="130" />
+      <el-table-column prop="indicatorName" label="指标" min-width="120" />
+      <el-table-column label="轮次" width="70" align="center">
+        <template slot-scope="s">第 {{ s.row.rectRound || 1 }} 轮</template>
+      </el-table-column>
+      <el-table-column label="巡检/及格" width="100">
+        <template slot-scope="s">{{ fmtNum(s.row.inspectScore) }} / {{ fmtNum(s.row.passScore) }}</template>
+      </el-table-column>
+      <el-table-column label="截止日期" width="115">
+        <template slot-scope="s">{{ fmt(s.row.deadlineDate, true) }}</template>
+      </el-table-column>
+      <el-table-column label="反馈日期" width="115">
+        <template slot-scope="s">{{ fmt(s.row.feedbackDate, true) }}</template>
+      </el-table-column>
+      <el-table-column label="复检日期" width="115">
+        <template slot-scope="s">{{ fmt(s.row.recheckDate, true) }}</template>
+      </el-table-column>
+      <el-table-column label="催办" width="80" align="center">
+        <template slot-scope="s">
+          <span v-if="s.row.urgeCount">{{ s.row.urgeCount }} 次</span>
+          <span v-else class="muted">-</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="状态" width="100">
+        <template slot-scope="s">
+          <el-tag size="mini" :style="rectStyle(s.row.status)">{{ rectText(s.row.status) }}</el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" width="150" fixed="right">
+        <template slot-scope="s">
+          <el-button type="text" @click="openDetail(s.row)">详情</el-button>
+          <el-button type="text" v-if="canUrge(s.row)" @click="urge(s.row)">催办</el-button>
+          <el-button type="text" v-if="s.row.status === 3" @click="openRecheck(s.row)">复检</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <div class="pager">
+      <el-pagination
+        background
+        layout="total, sizes, prev, pager, next, jumper"
+        :total="total"
+        :page-size="query.pageSize"
+        :current-page="query.currentPage"
+        @size-change="n => { query.pageSize = n; load() }"
+        @current-change="n => { query.currentPage = n; load() }"
+      />
+    </div>
+
+    <el-dialog title="批量设置截止日期" :visible.sync="batchVisible" width="420px">
+      <el-form label-width="100px">
+        <el-form-item label="已选记录">{{ selected.length }} 条</el-form-item>
+        <el-form-item label="截止日期" required>
+          <el-date-picker v-model="batchDeadline" type="datetime" value-format="yyyy-MM-dd HH:mm:ss" placeholder="选择截止日期时间" style="width: 100%;" />
+        </el-form-item>
+      </el-form>
+      <span slot="footer">
+        <el-button @click="batchVisible = false">取消</el-button>
+        <el-button type="primary" :loading="batchSaving" @click="batchUpdate">确定</el-button>
+      </span>
+    </el-dialog>
+
+    <rect-detail :visible.sync="detailVisible" :row="detailRow" @recheck="openRecheck" @saved="load" />
+    <rect-recheck :visible.sync="recheckVisible" :row="recheckRow" @saved="onRechecked" />
+  </section>
+</template>
+
+<script>
+import { post, ok, msg, pageData, fmt, fmtNum, slashDate, cleanParams, RECT_STATUS } from '../common'
+import RectDetail from './Detail.vue'
+import RectRecheck from './Recheck.vue'
+
+export default {
+  name: 'InspectRectificationIndex',
+  components: { RectDetail, RectRecheck },
+  data() {
+    return {
+      query: { storeId: '', indicatorId: '', status: '', deadlineDateBegin: '', deadlineDateEnd: '', pageSize: 10, currentPage: 1 },
+      deadlineRange: [],
+      rows: [],
+      total: 0,
+      loading: false,
+      stats: { 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0 },
+      selected: [],
+      stores: [],
+      indicators: [],
+      batchVisible: false,
+      batchDeadline: '',
+      batchSaving: false,
+      detailVisible: false,
+      detailRow: null,
+      recheckVisible: false,
+      recheckRow: null
+    }
+  },
+  computed: {
+    statItems() {
+      const defs = [
+        { key: 1, label: '待整改', color: '#E6A23C' },
+        { key: 2, label: '整改中', color: '#409EFF' },
+        { key: 3, label: '待复检', color: '#9B59B6' },
+        { key: 6, label: '已超期', color: '#909399' },
+        { key: 4, label: '复检通过', color: '#67C23A' }
+      ]
+      return defs.map(d => ({ label: d.label, color: d.color, value: this.stats[d.key] || 0 }))
+    }
+  },
+  created() {
+    this.load()
+    this.loadOptions()
+    this.loadStats()
+  },
+  methods: {
+    loadAll() {
+      this.load()
+      this.loadStats()
+    },
+    loadStats() {
+      post(this.$axios, '/inspectManage/rectification/page', { pageSize: 500, currentPage: 1 }).then(res => {
+        const list = (res && res.list) || []
+        const count = { 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0 }
+        list.forEach(r => { if (count[r.status] !== undefined) count[r.status]++ })
+        this.stats = count
+      })
+    },
+    load() {
+      const q = Object.assign({}, this.query)
+      q.deadlineDateBegin = this.deadlineRange && this.deadlineRange[0] ? slashDate(this.deadlineRange[0]) : ''
+      q.deadlineDateEnd = this.deadlineRange && this.deadlineRange[1] ? slashDate(this.deadlineRange[1]) : ''
+      this.loading = true
+      post(this.$axios, '/inspectManage/rectification/page', cleanParams(q)).then(res => {
+        this.rows = (res && res.list) || []
+        this.total = pageData(res).resultCount || this.rows.length
+      }).finally(() => { this.loading = false })
+    },
+    loadOptions() {
+      post(this.$axios, '/storeManage/getStoreList', {}).then(res => { this.stores = (res && res.list) || [] })
+      post(this.$axios, '/inspectManage/indicator/page', { pageSize: 200, currentPage: 1 }).then(res => { this.indicators = (res && res.list) || [] })
+    },
+    reset() {
+      this.query = { storeId: '', indicatorId: '', status: '', deadlineDateBegin: '', deadlineDateEnd: '', pageSize: 10, currentPage: 1 }
+      this.deadlineRange = []
+      this.load()
+    },
+    onSelect(rows) { this.selected = rows },
+    canUrge(row) { return [1, 2, 3, 5, 6].indexOf(row.status) !== -1 },
+    urge(row) {
+      this.$confirm('确认对该整改进行催办?', '提示').then(() => {
+        post(this.$axios, '/inspectManage/rectification/urge', { id: row.rectId }).then(res => {
+          if (ok(res)) { this.load(); this.$message.success('催办完成') } else { this.$message.error(msg(res) || '催办失败') }
+        })
+      }).catch(() => {})
+    },
+    openDetail(row) {
+      this.detailRow = row
+      this.detailVisible = true
+    },
+    openRecheck(row) {
+      this.recheckRow = row
+      this.recheckVisible = true
+    },
+    onRechecked() {
+      this.recheckVisible = false
+      this.load()
+    },
+    batchUpdate() {
+      if (!this.batchDeadline) return this.$message.warning('请选择截止日期')
+      this.batchSaving = true
+      post(this.$axios, '/inspectManage/rectification/batchUpdateDeadline', {
+        rectIds: this.selected.map(r => r.rectId),
+        deadlineDate: this.batchDeadline
+      }).then(res => {
+        if (ok(res)) {
+          this.batchVisible = false
+          this.batchDeadline = ''
+          this.load()
+          this.$message.success('截止日期更新完成')
+        } else {
+          this.$message.error(msg(res) || '更新失败')
+        }
+      }).finally(() => { this.batchSaving = false })
+    },
+    rectText(v) { return (RECT_STATUS[v] || {}).text || '-' },
+    rectStyle(v) {
+      const st = RECT_STATUS[v]
+      return { background: st.color, borderColor: st.color, color: '#fff' }
+    },
+    fmt,
+    fmtNum
+  }
+}
+</script>
+
+<style scoped>
+.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-select, .toolbar .el-input { width: 160px; }
+.grow { flex: 1; }
+.muted { color: #909399; }
+.pager { margin-top: 16px; text-align: right; }
+.stat-cards { display: flex; gap: 16px; margin-bottom: 16px; }
+.stat-card { flex: 1; background: #f5f7fa; border-radius: 6px; padding: 14px 18px; display: flex; align-items: baseline; gap: 8px; }
+.stat-card span { color: #909399; font-size: 13px; }
+.stat-card b { font-size: 24px; color: #303133; }
+.stat-card em { color: #909399; font-style: normal; font-size: 12px; }
+</style>

+ 144 - 0
src/components/page/admin/inspect/rectification/Recheck.vue

@@ -0,0 +1,144 @@
+<template>
+  <el-dialog title="整改复检" :visible.sync="dialogVisible" width="520px" @open="init">
+    <div class="recheck-head" v-if="row">
+      门店:{{ row.storeName }} · 指标:{{ row.indicatorName }}(第 {{ row.rectRound || 1 }} 轮)
+    </div>
+    <el-form :model="form" label-width="100px">
+      <el-form-item label="复检结果" required>
+        <el-radio-group v-model="form.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-number v-model="form.recheckScore" :min="0" :max="maxScore" :step="1" :precision="1" />
+        <span class="max-hint" v-if="maxScore > 0">最高 {{ maxScore }} 分(及格分×2)</span>
+      </el-form-item>
+      <el-form-item label="复检照片">
+        <div class="pic-list">
+          <div v-for="(p, i) in form.recheckPictures" :key="i" class="pic-item">
+            <el-image :src="p" fit="cover" :preview-src-list="form.recheckPictures" class="pic-img" />
+            <i class="el-icon-close pic-del" @click="removePic(i)"></i>
+          </div>
+          <el-upload
+            v-if="form.recheckPictures.length < 5"
+            :http-request="opts => uploadPic(opts)"
+            :show-file-list="false"
+            accept="image/*"
+            action="#">
+            <div class="pic-add"><i class="el-icon-plus"></i><span>上传</span></div>
+          </el-upload>
+        </div>
+      </el-form-item>
+      <el-form-item label="复检备注">
+        <el-input v-model="form.recheckRmk" type="textarea" :rows="3" placeholder="复检意见" />
+      </el-form-item>
+    </el-form>
+    <div class="tip" v-if="form.recheckResult === 0">复检不通过时将自动生成新一轮整改(最多3轮)。</div>
+    <span slot="footer">
+      <el-button @click="dialogVisible = false">取消</el-button>
+      <el-button type="primary" :loading="saving" @click="save">提交复检</el-button>
+    </span>
+  </el-dialog>
+</template>
+
+<script>
+import { post, ok, msg, splitPictures, joinPictures, upload, currentUser } from '../common'
+
+export default {
+  name: 'InspectRectificationRecheck',
+  props: {
+    visible: Boolean,
+    row: Object
+  },
+  computed: {
+    dialogVisible: {
+      get() { return this.visible },
+      set(v) { this.$emit('update:visible', v) }
+    },
+    maxScore() {
+      const pass = Number(this.row && this.row.passScore)
+      return pass > 0 ? Math.round(pass * 2 * 100) / 100 : 100
+    }
+  },
+  data() {
+    return {
+      saving: false,
+      form: { recheckResult: 1, recheckScore: 60, recheckPictures: [], recheckRmk: '', recheckUserId: null, recheckUserName: '' }
+    }
+  },
+  methods: {
+    init() {
+      const me = currentUser()
+      const pass = Number(this.row && this.row.passScore)
+      this.form = {
+        recheckResult: 1,
+        recheckScore: pass > 0 ? pass : 60,
+        recheckPictures: splitPictures(this.row && this.row.recheckPictures),
+        recheckRmk: this.row ? this.row.recheckRmk || '' : '',
+        recheckUserId: me.userId,
+        recheckUserName: me.userName
+      }
+    },
+    removePic(i) { this.form.recheckPictures.splice(i, 1) },
+    uploadPic(opts) {
+      if (opts.file.size > 204800) {
+        this.$message.error('单张图片不能超过 200KB')
+        opts.onError(new Error('too large'))
+        return
+      }
+      upload(this.$axios, opts.file, '/rectification').then(res => {
+        if (ok(res) && res.obj) {
+          this.form.recheckPictures.push(res.obj)
+          if (this.form.recheckPictures.length > 5) {
+            this.form.recheckPictures.splice(5, this.form.recheckPictures.length - 5)
+            this.$message.warning('每项最多上传 5 张图片')
+          }
+          opts.onSuccess(res)
+        } else {
+          this.$message.error(msg(res) || '上传失败')
+          opts.onError(new Error('upload failed'))
+        }
+      }).catch(() => {
+        this.$message.error('上传失败')
+        opts.onError(new Error('upload failed'))
+      })
+    },
+    save() {
+      if (this.form.recheckResult === null || this.form.recheckResult === undefined) {
+        return this.$message.warning('请选择复检结果')
+      }
+      this.saving = true
+      post(this.$axios, '/inspectManage/rectification/recheck', {
+        id: this.row.rectId,
+        recheckScore: this.form.recheckScore,
+        recheckResult: this.form.recheckResult,
+        recheckPictures: joinPictures(this.form.recheckPictures),
+        recheckRmk: this.form.recheckRmk,
+        recheckUserId: this.form.recheckUserId,
+        recheckUserName: this.form.recheckUserName
+      }).then(res => {
+        if (ok(res)) {
+          this.$message.success('复检完成')
+          this.$emit('saved')
+        } else {
+          this.$message.error(msg(res) || '复检失败')
+        }
+      }).finally(() => { this.saving = false })
+    }
+  }
+}
+</script>
+
+<style scoped>
+.recheck-head { background: #f5f7fa; border-radius: 4px; padding: 10px 14px; margin-bottom: 14px; color: #606266; font-size: 13px; }
+.max-hint { color: #909399; font-size: 12px; margin-left: 10px; }
+.pic-list { display: flex; flex-wrap: wrap; gap: 8px; }
+.pic-item { position: relative; }
+.pic-img { width: 60px; height: 60px; border-radius: 4px; display: block; }
+.pic-del { position: absolute; top: -6px; right: -6px; background: #f56c6c; color: #fff; border-radius: 50%; width: 16px; height: 16px; font-size: 10px; line-height: 16px; text-align: center; cursor: pointer; }
+.pic-add { width: 60px; height: 60px; border: 1px dashed #dcdfe6; border-radius: 4px; display: flex; flex-direction: column; align-items: center; justify-content: center; color: #909399; cursor: pointer; }
+.pic-add i { font-size: 18px; }
+.pic-add span { font-size: 11px; }
+.tip { color: #e6a23c; font-size: 12px; padding: 0 0 4px 100px; }
+</style>

+ 296 - 0
src/components/page/admin/inspect/task/Execute.vue

@@ -0,0 +1,296 @@
+<template>
+  <el-dialog title="执行巡检" :visible.sync="dialogVisible" width="860px" @open="load" :close-on-click-modal="false">
+    <div class="exec-wrap" v-loading="loading">
+      <!-- 任务信息 -->
+      <div class="task-head" v-if="task">
+        <div class="task-title">{{ task.storeName }} · {{ task.planName }}</div>
+        <div class="task-meta">
+          <span>执行日期:{{ fmt(task.taskDate) }}</span>
+          <span>巡检员:{{ task.inspectorName || '-' }}</span>
+          <span v-if="task.status === 1">
+            <el-tag size="mini" type="warning">待执行</el-tag>
+          </span>
+          <span v-else-if="task.status === 2">
+            <el-tag size="mini" type="primary">执行中</el-tag>
+          </span>
+          <el-button v-if="task.status === 1" type="primary" size="mini" :loading="starting" @click="start">开始执行</el-button>
+        </div>
+        <el-alert v-if="task.status === 3" type="success" :closable="false" title="任务已提交,不可修改" />
+      </div>
+
+      <!-- 自动采集指标 -->
+      <div class="sec" v-if="autoItems.length">
+        <h4>自动采集指标(只读)</h4>
+        <div v-for="item in autoItems" :key="item.taskItemId" class="auto-item">
+          <div class="ai-left">
+            <span class="ai-name">{{ item.indicatorName }}</span>
+            <el-tag size="mini" type="primary">自动</el-tag>
+          </div>
+          <div class="ai-right">
+            <span class="ai-raw" v-if="item.rawValueText">原始值:{{ item.rawValueText }}</span>
+            <span class="ai-score">得分:{{ fmtNum(item.score) }}/{{ fmtNum(item.maxScore) }}</span>
+            <el-tag size="mini" :type="item.passFlag === 1 ? 'success' : 'danger'">{{ item.passFlag === 1 ? '及格' : '不及格' }}</el-tag>
+            <span class="ai-weight">权重 {{ fmtNum(item.weight) }}%</span>
+          </div>
+        </div>
+      </div>
+
+      <!-- 人工巡检指标 -->
+      <div class="sec" v-if="manualItems.length">
+        <h4>人工巡检指标(需打分)</h4>
+        <div v-for="item in manualItems" :key="item.taskItemId" class="manual-item">
+          <div class="mi-head">
+            <span class="ai-name">{{ item.indicatorName }}</span>
+            <el-tag size="mini" type="warning">人工</el-tag>
+            <span class="mi-pass" v-if="scored(item)">
+              <el-tag size="mini" :type="itemPass(item) ? 'success' : 'danger'">{{ itemPass(item) ? '及格' : '不及格' }}</el-tag>
+            </span>
+            <span class="grow"></span>
+            <span class="mi-weight">权重 {{ fmtNum(item.weight) }}% · 满分 {{ fmtNum(item.maxScore) }} · 及格 {{ fmtNum(item.passScore) }}</span>
+          </div>
+          <div class="mi-body">
+            <div class="mi-score">
+              <span class="mi-label">得分</span>
+              <el-input-number v-model="item.score" :min="0" :max="Number(item.maxScore || 0)" :step="1" :precision="1" :disabled="!canEdit" @change="onScoreChange" />
+              <span class="mi-unit">/ {{ fmtNum(item.maxScore) }}</span>
+            </div>
+            <div class="mi-pics">
+              <span class="mi-label">照片</span>
+              <div class="pic-list">
+                <div v-for="(p, idx) in item._pics" :key="idx" class="pic-item">
+                  <el-image :src="p" fit="cover" :preview-src-list="item._pics" class="pic-img" />
+                  <i class="el-icon-close pic-del" v-if="canEdit" @click="removePic(item, idx)"></i>
+                </div>
+                <el-upload
+                  v-if="canEdit && item._pics.length < 5"
+                  :http-request="opts => uploadItemPic(item, opts)"
+                  :show-file-list="false"
+                  accept="image/*"
+                  action="#">
+                  <div class="pic-add"><i class="el-icon-plus"></i><span>上传</span></div>
+                </el-upload>
+              </div>
+            </div>
+            <div class="mi-rmk">
+              <span class="mi-label">备注</span>
+              <el-input v-model="item.rmk" :disabled="!canEdit" type="textarea" :rows="2" placeholder="巡检备注" />
+            </div>
+          </div>
+        </div>
+      </div>
+
+      <!-- 汇总 -->
+      <div class="summary">
+        <div class="sum-item"><span>加权总分</span><b class="sum-total">{{ fmtNum(totalScore) }}</b><em>/ {{ fmtNum(maxScore) }}</em></div>
+        <div class="sum-item"><span>及格线</span><b>{{ fmtNum(passScore) }}</b></div>
+        <div class="sum-item">
+          <span>结果</span>
+          <el-tag v-if="task && task.status === 3" size="small" :type="task.passFlag === 1 ? 'success' : 'danger'">{{ task.passFlag === 1 ? '及格' : '不及格' }}</el-tag>
+          <el-tag v-else-if="allScored" size="small" :type="passFlag ? 'success' : 'danger'">{{ passFlag ? '及格' : '不及格' }}</el-tag>
+          <el-tag v-else size="small" type="info">未完成</el-tag>
+        </div>
+        <div class="sum-item grow">
+          <span>不合格项</span>
+          <span class="unpass" v-if="unpassItems.length">{{ unpassItems.join('、') }}</span>
+          <span v-else class="muted">无</span>
+        </div>
+      </div>
+    </div>
+
+    <span slot="footer" class="exec-footer">
+      <span class="hint">提交后系统将自动生成整改记录(如存在不合格项)</span>
+      <el-button @click="dialogVisible = false">关闭</el-button>
+      <el-button v-if="canEdit" :loading="savingDraft" @click="saveDraft">保存草稿</el-button>
+      <el-button v-if="canEdit" type="primary" :loading="submitting" @click="submit">提交巡检</el-button>
+    </span>
+  </el-dialog>
+</template>
+
+<script>
+import { post, ok, msg, fmt, fmtNum, splitPictures, joinPictures, upload } from '../common'
+
+export default {
+  name: 'InspectTaskExecute',
+  props: {
+    visible: Boolean,
+    taskId: [Number, String]
+  },
+  computed: {
+    dialogVisible: {
+      get() { return this.visible },
+      set(v) { this.$emit('update:visible', v) }
+    },
+    autoItems() { return this.items.filter(i => i.indicatorType === 1) },
+    manualItems() { return this.items.filter(i => i.indicatorType === 2) },
+    canEdit() { return !!(this.task && this.task.status === 2) },
+    totalScore() {
+      return this.items.reduce((s, i) => s + ((Number(i.score) || 0) * (Number(i.weight) || 0) / 100), 0)
+    },
+    maxScore() { return Number((this.task && this.task.maxScore) || 0) },
+    passScore() { return Number((this.task && this.task.passScore) || 0) },
+    passFlag() { return this.totalScore >= this.passScore },
+    allScored() {
+      return this.manualItems.every(i => i.score !== null && i.score !== undefined && i.score !== '')
+    },
+    unpassItems() {
+      return this.items.filter(i => i.score !== null && i.score !== undefined && i.score !== '' && Number(i.score) < Number(i.passScore || 0)).map(i => i.indicatorName)
+    }
+  },
+  data() {
+    return { loading: false, task: null, items: [], starting: false, savingDraft: false, submitting: false }
+  },
+  methods: {
+    load() {
+      if (!this.taskId) return
+      this.loading = true
+      Promise.all([
+        post(this.$axios, '/inspectManage/task/detail', { id: this.taskId }),
+        post(this.$axios, '/inspectManage/task/items', { taskId: this.taskId })
+      ]).then(([r1, r2]) => {
+        this.task = (r1 && r1.obj) || null
+        this.items = ((r2 && r2.list) || []).map(i => Object.assign({}, i, { _pics: splitPictures(i.pictureUrls) }))
+      }).finally(() => { this.loading = false })
+    },
+    start() {
+      this.starting = true
+      post(this.$axios, '/inspectManage/task/start', { id: this.taskId, inspectorId: this.task.inspectorId }).then(res => {
+        if (ok(res)) {
+          this.$message.success('已开始执行')
+          post(this.$axios, '/inspectManage/task/detail', { id: this.taskId }).then(r => {
+            this.task = (r && r.obj) || this.task
+          })
+        } else {
+          this.$message.error(msg(res) || '开始失败')
+        }
+      }).finally(() => { this.starting = false })
+    },
+    scored(item) {
+      return item.score !== null && item.score !== undefined && item.score !== ''
+    },
+    itemPass(item) {
+      return Number(item.score) >= Number(item.passScore || 0)
+    },
+    onScoreChange() {
+      // 实时计算由 computed 完成
+    },
+    beforeUpload(file) {
+      if (file.size > 204800) {
+        this.$message.error('单张图片不能超过 200KB')
+        return false
+      }
+      if (file.type && file.type.indexOf('image') !== 0) {
+        this.$message.error('仅支持图片文件')
+        return false
+      }
+      return true
+    },
+    uploadItemPic(item, opts) {
+      if (!this.beforeUpload(opts.file)) {
+        opts.onError(new Error('invalid'))
+        return
+      }
+      upload(this.$axios, opts.file, '/inspect').then(res => {
+        if (ok(res) && res.obj) {
+          item._pics.push(res.obj)
+          if (item._pics.length > 5) {
+            item._pics.splice(5, item._pics.length - 5)
+            this.$message.warning('每项最多上传 5 张图片')
+          }
+          opts.onSuccess(res)
+        } else {
+          this.$message.error(msg(res) || '上传失败')
+          opts.onError(new Error(msg(res) || 'upload failed'))
+        }
+      }).catch(() => {
+        this.$message.error('上传失败')
+        opts.onError(new Error('upload failed'))
+      })
+    },
+    removePic(item, idx) {
+      item._pics.splice(idx, 1)
+    },
+    itemPayload(item) {
+      const payload = Object.assign({}, item)
+      delete payload._pics
+      delete payload.createDate
+      delete payload.updateDate
+      payload.pictureUrls = joinPictures(item._pics)
+      return payload
+    },
+    saveDraft() {
+      const reqs = this.manualItems.map(item => post(this.$axios, '/inspectManage/taskItem/update', this.itemPayload(item)))
+      this.savingDraft = true
+      Promise.all(reqs).then(results => {
+        const failed = results.filter(r => !ok(r))
+        if (failed.length) {
+          this.$message.error('部分评分项保存失败')
+        } else {
+          this.$message.success('草稿已保存')
+        }
+      }).catch(() => this.$message.error('保存失败')).finally(() => { this.savingDraft = false })
+    },
+    submit() {
+      if (!this.allScored) return this.$message.warning('请先为所有人工巡检指标打分')
+      const invalid = this.manualItems.filter(i => Number(i.score) > Number(i.maxScore))
+      if (invalid.length) return this.$message.warning('存在超出满分范围的得分,请检查')
+      this.submitting = true
+      const reqs = this.manualItems.map(item => post(this.$axios, '/inspectManage/taskItem/update', this.itemPayload(item)))
+      Promise.all(reqs).then(results => {
+        const failed = results.filter(r => !ok(r))
+        if (failed.length) throw new Error('评分项保存失败')
+        return post(this.$axios, '/inspectManage/task/submit', { id: this.taskId, inspectorId: this.task.inspectorId })
+      }).then(res => {
+        if (ok(res)) {
+          this.$message.success('提交成功,已自动生成整改记录(如有不合格项)')
+          this.$emit('saved')
+        } else {
+          this.$message.error(msg(res) || '提交失败')
+        }
+      }).catch(err => {
+        this.$message.error(err && err.message ? err.message : '提交失败')
+      }).finally(() => { this.submitting = false })
+    },
+    fmt,
+    fmtNum
+  }
+}
+</script>
+
+<style scoped>
+.exec-wrap { max-height: 520px; overflow-y: auto; }
+.task-head { background: #f5f7fa; border-radius: 4px; padding: 12px 16px; margin-bottom: 14px; }
+.task-title { font-size: 15px; font-weight: 600; margin-bottom: 6px; }
+.task-meta { display: flex; gap: 18px; align-items: center; color: #606266; font-size: 13px; flex-wrap: wrap; }
+.sec { margin-bottom: 16px; }
+.sec h4 { margin: 0 0 8px; font-size: 13px; color: #606266; }
+.auto-item { display: flex; justify-content: space-between; align-items: center; background: #fafbfc; border: 1px solid #ebeef5; border-radius: 4px; padding: 10px 14px; margin-bottom: 8px; }
+.ai-left { display: flex; align-items: center; gap: 8px; }
+.ai-name { font-weight: 600; }
+.ai-right { display: flex; align-items: center; gap: 12px; font-size: 13px; }
+.ai-raw { color: #909399; }
+.ai-score { color: #606266; }
+.ai-weight { color: #909399; }
+.manual-item { border: 1px solid #ebeef5; border-radius: 4px; margin-bottom: 10px; }
+.mi-head { display: flex; align-items: center; gap: 8px; padding: 10px 14px; background: #fafbfc; border-bottom: 1px solid #ebeef5; }
+.grow { flex: 1; }
+.mi-weight { color: #909399; font-size: 12px; }
+.mi-body { padding: 12px 14px; }
+.mi-score, .mi-pics, .mi-rmk { display: flex; align-items: flex-start; gap: 10px; margin-bottom: 10px; }
+.mi-label { width: 48px; color: #909399; font-size: 13px; line-height: 32px; flex-shrink: 0; }
+.mi-unit { line-height: 32px; color: #909399; }
+.pic-list { display: flex; flex-wrap: wrap; gap: 8px; }
+.pic-item { position: relative; }
+.pic-img { width: 60px; height: 60px; border-radius: 4px; display: block; }
+.pic-del { position: absolute; top: -6px; right: -6px; background: #f56c6c; color: #fff; border-radius: 50%; width: 16px; height: 16px; font-size: 10px; line-height: 16px; text-align: center; cursor: pointer; }
+.pic-add { width: 60px; height: 60px; border: 1px dashed #dcdfe6; border-radius: 4px; display: flex; flex-direction: column; align-items: center; justify-content: center; color: #909399; cursor: pointer; }
+.pic-add i { font-size: 18px; }
+.pic-add span { font-size: 11px; }
+.summary { display: flex; align-items: center; gap: 26px; background: #f5f7fa; border-radius: 4px; padding: 12px 16px; margin-top: 4px; }
+.sum-item { display: flex; align-items: baseline; gap: 6px; font-size: 13px; color: #606266; }
+.sum-total { font-size: 22px; color: #409eff; }
+.sum-item em { color: #909399; font-style: normal; }
+.unpass { color: #f56c6c; }
+.muted { color: #909399; }
+.exec-footer { display: flex; align-items: center; gap: 10px; }
+.hint { color: #909399; font-size: 12px; flex: 1; }
+</style>

+ 224 - 0
src/components/page/admin/inspect/task/Index.vue

@@ -0,0 +1,224 @@
+<template>
+  <section class="panel">
+    <div class="toolbar">
+      <el-select v-model="query.planId" placeholder="巡检计划" clearable filterable style="width: 180px;">
+        <el-option v-for="p in plans" :key="p.planId" :label="p.planName" :value="p.planId" />
+      </el-select>
+      <el-select v-model="query.storeId" placeholder="所属门店" clearable filterable style="width: 180px;">
+        <el-option v-for="s in stores" :key="s.storeId" :label="s.storeName" :value="s.storeId" />
+      </el-select>
+      <el-select v-model="query.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-select v-model="query.passFlag" placeholder="考核结果" clearable>
+        <el-option label="及格" :value="1" />
+        <el-option label="不及格" :value="0" />
+      </el-select>
+      <el-date-picker v-model="dateRange" type="daterange" value-format="yyyy-MM-dd" start-placeholder="开始日期" end-placeholder="结束日期" style="width: 240px;" />
+      <el-button type="primary" @click="load()">查询</el-button>
+      <el-button @click="reset">重置</el-button>
+      <span class="grow"></span>
+      <el-button type="primary" plain @click="loadAll()">刷新</el-button>
+    </div>
+
+    <!-- 任务统计 -->
+    <div class="stat-cards">
+      <div class="stat-card"><span>任务总数</span><b>{{ stats.total }}</b><em>条</em></div>
+      <div class="stat-card"><span>已完成</span><b>{{ stats.finish }}</b><em>条 · 完成率 {{ stats.finishRate }}%</em></div>
+      <div class="stat-card"><span>及格率</span><b>{{ stats.passRate }}</b><em>%</em></div>
+      <div class="stat-card"><span>平均得分</span><b>{{ stats.avg }}</b><em>分</em></div>
+    </div>
+
+    <el-table :data="rows" border stripe v-loading="loading">
+      <el-table-column prop="taskId" label="任务ID" width="80" />
+      <el-table-column prop="planName" label="巡检计划" min-width="130" />
+      <el-table-column prop="storeName" label="门店" min-width="140" />
+      <el-table-column label="执行日期" width="110">
+        <template slot-scope="s">{{ fmt(s.row.taskDate) }}</template>
+      </el-table-column>
+      <el-table-column prop="inspectorName" label="巡检员" width="90">
+        <template slot-scope="s">{{ s.row.inspectorName || '-' }}</template>
+      </el-table-column>
+      <el-table-column label="得分" width="150">
+        <template slot-scope="s">
+          <div v-if="s.row.status === 3" class="score-cell">
+            <el-progress :percentage="scorePct(s.row)" :status="scoreStatus(s.row)" :stroke-width="10" :show-text="false" />
+            <span class="score-text">{{ fmtNum(s.row.totalScore) }}/{{ fmtNum(s.row.maxScore) }}</span>
+          </div>
+          <span v-else class="muted">-</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="结果" width="85">
+        <template slot-scope="s">
+          <el-tag v-if="s.row.status === 3" size="mini" :type="s.row.passFlag === 1 ? 'success' : 'danger'">{{ s.row.passFlag === 1 ? '及格' : '不及格' }}</el-tag>
+          <span v-else class="muted">-</span>
+        </template>
+      </el-table-column>
+      <el-table-column prop="unpassItemCount" label="不合格项" width="85" align="center">
+        <template slot-scope="s">{{ s.row.unpassItemCount || 0 }}</template>
+      </el-table-column>
+      <el-table-column label="状态" width="90">
+        <template slot-scope="s"><el-tag size="mini" :type="taskStatus(s.row.status).type">{{ taskStatus(s.row.status).text }}</el-tag></template>
+      </el-table-column>
+      <el-table-column label="操作" width="170" fixed="right">
+        <template slot-scope="s">
+          <el-button type="text" v-if="s.row.status === 1 || s.row.status === 2" @click="openExecute(s.row)">执行</el-button>
+          <el-button type="text" v-if="s.row.status === 3" @click="openReport(s.row)">报告</el-button>
+          <el-button type="text" v-if="s.row.status === 1 || s.row.status === 2" class="danger" @click="cancel(s.row)">取消</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <div class="pager">
+      <el-pagination
+        background
+        layout="total, sizes, prev, pager, next, jumper"
+        :total="total"
+        :page-size="query.pageSize"
+        :current-page="query.currentPage"
+        @size-change="n => { query.pageSize = n; load() }"
+        @current-change="n => { query.currentPage = n; load() }"
+      />
+    </div>
+
+    <task-execute :visible.sync="executeVisible" :task-id="currentId" @saved="onSaved" />
+    <task-report :visible.sync="reportVisible" :task-id="currentId" />
+  </section>
+</template>
+
+<script>
+import { post, ok, msg, pageData, fmt, fmtNum, slashDate, cleanParams, TASK_STATUS } from '../common'
+import TaskExecute from './Execute.vue'
+import TaskReport from './Report.vue'
+
+export default {
+  name: 'InspectTaskIndex',
+  components: { TaskExecute, TaskReport },
+  data() {
+    return {
+      query: { planId: '', storeId: '', status: '', passFlag: '', taskDateBegin: '', taskDateEnd: '', pageSize: 10, currentPage: 1 },
+      dateRange: [],
+      rows: [],
+      total: 0,
+      loading: false,
+      stats: { total: 0, finish: 0, finishRate: 0, passRate: 0, avg: '0.0' },
+      plans: [],
+      stores: [],
+      executeVisible: false,
+      reportVisible: false,
+      currentId: null
+    }
+  },
+  created() {
+    this.load()
+    this.loadOptions()
+    this.loadStats()
+  },
+  methods: {
+    loadAll() {
+      this.load()
+      this.loadStats()
+    },
+    load() {
+      const q = Object.assign({}, this.query)
+      q.taskDateBegin = this.dateRange && this.dateRange[0] ? slashDate(this.dateRange[0]) : ''
+      q.taskDateEnd = this.dateRange && this.dateRange[1] ? slashDate(this.dateRange[1]) : ''
+      this.loading = true
+      post(this.$axios, '/inspectManage/task/page', cleanParams(q)).then(res => {
+        this.rows = (res && res.list) || []
+        this.total = pageData(res).resultCount || this.rows.length
+      }).finally(() => { this.loading = false })
+    },
+    loadStats() {
+      post(this.$axios, '/inspectManage/task/statistics', {}).then(res => {
+        const rows = (res && res.list) || []
+        let total = 0
+        let finish = 0
+        let pass = 0
+        let scoreSum = 0
+        rows.forEach(r => {
+          const arr = Array.isArray(r) ? r : Object.keys(r).map(k => r[k])
+          const count = Number(arr[1] || 0)
+          const finishCount = Number(arr[2] || 0)
+          const passCount = Number(arr[3] || 0)
+          const avgScore = Number(arr[4] || 0)
+          total += count
+          finish += finishCount
+          pass += passCount
+          scoreSum += avgScore * count
+        })
+        this.stats = {
+          total,
+          finish,
+          finishRate: total ? Math.round(finish / total * 100) : 0,
+          passRate: finish ? Math.round(pass / finish * 100) : 0,
+          avg: total ? (scoreSum / total).toFixed(1) : '0.0'
+        }
+      })
+    },
+    loadOptions() {
+      post(this.$axios, '/inspectManage/plan/page', { pageSize: 200, currentPage: 1 }).then(res => {
+        this.plans = (res && res.list) || []
+      })
+      post(this.$axios, '/storeManage/getStoreList', {}).then(res => {
+        this.stores = (res && res.list) || []
+      })
+    },
+    reset() {
+      this.query = { planId: '', storeId: '', status: '', passFlag: '', taskDateBegin: '', taskDateEnd: '', pageSize: 10, currentPage: 1 }
+      this.dateRange = []
+      this.load()
+    },
+    openExecute(row) {
+      this.currentId = row.taskId
+      this.executeVisible = true
+    },
+    openReport(row) {
+      this.currentId = row.taskId
+      this.reportVisible = true
+    },
+    onSaved() {
+      this.executeVisible = false
+      this.load()
+    },
+    cancel(row) {
+      this.$confirm('确认取消该任务?', '提示').then(() => {
+        post(this.$axios, '/inspectManage/task/cancel', { id: row.taskId }).then(res => {
+          if (ok(res)) { this.load(); this.$message.success('取消完成') } else { this.$message.error(msg(res) || '取消失败') }
+        })
+      }).catch(() => {})
+    },
+    scorePct(row) {
+      const max = Number(row.maxScore || 0)
+      return max > 0 ? Math.round(Number(row.totalScore || 0) / max * 100) : 0
+    },
+    scoreStatus(row) {
+      return row.passFlag === 1 ? 'success' : 'exception'
+    },
+    taskStatus(v) { return TASK_STATUS[v] || { text: '-', type: 'info' } },
+    fmt,
+    fmtNum
+  }
+}
+</script>
+
+<style scoped>
+.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-select, .toolbar .el-input { width: 160px; }
+.grow { flex: 1; }
+.danger { color: #f56c6c; }
+.muted { color: #909399; }
+.pager { margin-top: 16px; text-align: right; }
+.score-cell { display: flex; align-items: center; gap: 8px; }
+.score-cell .el-progress { flex: 1; }
+.score-text { width: 70px; font-size: 12px; color: #606266; white-space: nowrap; }
+.stat-cards { display: flex; gap: 16px; margin-bottom: 16px; }
+.stat-card { flex: 1; background: #f5f7fa; border-radius: 6px; padding: 14px 18px; display: flex; align-items: baseline; gap: 8px; }
+.stat-card span { color: #909399; font-size: 13px; }
+.stat-card b { font-size: 24px; color: #303133; }
+.stat-card em { color: #909399; font-style: normal; font-size: 12px; }
+</style>

+ 266 - 0
src/components/page/admin/inspect/task/Report.vue

@@ -0,0 +1,266 @@
+<template>
+  <el-dialog title="巡检报告" :visible.sync="dialogVisible" width="920px" @open="load">
+    <div class="report-wrap" v-loading="loading">
+      <!-- 报告头 -->
+      <div class="report-head" v-if="task">
+        <div class="rh-left">
+          <div class="rh-title">{{ task.storeName }}</div>
+          <div class="rh-meta">巡检日期:{{ fmt(task.taskDate) }} | 巡检员:{{ task.inspectorName || '-' }} | 计划:{{ task.planName }}</div>
+        </div>
+        <div class="rh-score">
+          <span class="rh-num" :class="{ fail: task.passFlag !== 1 }">{{ fmtNum(task.totalScore) }}</span>
+          <span class="rh-unit">加权总分 / {{ fmtNum(task.maxScore) }}</span>
+        </div>
+        <el-tag size="small" :type="task.passFlag === 1 ? 'success' : 'danger'" class="rh-tag">{{ task.passFlag === 1 ? '✓ 及格' : '✗ 不及格' }}</el-tag>
+      </div>
+
+      <div class="report-grid">
+        <!-- 雷达图 -->
+        <div class="chart-card">
+          <h4>指标得分雷达图</h4>
+          <canvas ref="radar" width="360" height="300" class="radar-canvas"></canvas>
+          <div class="legend-line">
+            <span class="legend-dot ok"></span>及格项
+            <span class="legend-dot fail"></span>不及格项
+          </div>
+        </div>
+
+        <!-- 各指标得分明细 -->
+        <div class="chart-card">
+          <h4>各指标得分明细</h4>
+          <div v-for="item in items" :key="item.taskItemId" class="ind-card" :class="{ 'ind-fail': item.passFlag !== 1 }">
+            <div class="ind-head">
+              <span class="ind-name">{{ item.indicatorName }}</span>
+              <el-tag size="mini" :type="item.indicatorType === 1 ? 'primary' : 'warning'">{{ item.indicatorType === 1 ? '自动' : '人工' }}</el-tag>
+              <span class="ind-raw" v-if="item.rawValueText">原始值:{{ item.rawValueText }}</span>
+            </div>
+            <div class="ind-body">
+              <div class="score-bar">
+                <div class="score-track">
+                  <div class="score-fill" :class="item.passFlag === 1 ? 'fill-ok' : 'fill-fail'" :style="{ width: barWidth(item) + '%' }"></div>
+                </div>
+                <span class="score-val">{{ fmtNum(item.score) }}/{{ fmtNum(item.maxScore) }}</span>
+              </div>
+              <el-tag size="mini" :type="item.passFlag === 1 ? 'success' : 'danger'">{{ item.passFlag === 1 ? '及格' : '不及格' }}</el-tag>
+              <span class="ind-photo" v-if="pictures(item).length">
+                <el-image
+                  v-for="(p, i) in pictures(item)"
+                  :key="i"
+                  :src="p"
+                  fit="cover"
+                  :preview-src-list="pictures(item)"
+                  class="thumb-sm" />
+              </span>
+            </div>
+          </div>
+        </div>
+      </div>
+
+      <!-- 逐项明细表 -->
+      <div class="detail-block">
+        <h4>逐项明细</h4>
+        <el-table :data="items" border size="mini">
+          <el-table-column prop="indicatorName" label="指标" min-width="110">
+            <template slot-scope="s"><span :class="{ 'row-fail': s.row.passFlag !== 1 }">{{ s.row.indicatorName }}</span></template>
+          </el-table-column>
+          <el-table-column label="类型" width="75">
+            <template slot-scope="s"><el-tag size="mini" :type="s.row.indicatorType === 1 ? 'primary' : 'warning'">{{ s.row.indicatorType === 1 ? '自动' : '人工' }}</el-tag></template>
+          </el-table-column>
+          <el-table-column prop="weight" label="权重" width="65" align="center">
+            <template slot-scope="s">{{ fmtNum(s.row.weight) }}%</template>
+          </el-table-column>
+          <el-table-column label="原始值" width="90">
+            <template slot-scope="s">{{ s.row.rawValueText || '-' }}</template>
+          </el-table-column>
+          <el-table-column label="得分" width="90" align="center">
+            <template slot-scope="s">{{ fmtNum(s.row.score) }}/{{ fmtNum(s.row.maxScore) }}</template>
+          </el-table-column>
+          <el-table-column label="加权分" width="70" align="center">
+            <template slot-scope="s">{{ fmtNum(s.row.weightedScore) }}</template>
+          </el-table-column>
+          <el-table-column label="结果" width="75">
+            <template slot-scope="s"><el-tag size="mini" :type="s.row.passFlag === 1 ? 'success' : 'danger'">{{ s.row.passFlag === 1 ? '及格' : '不及格' }}</el-tag></template>
+          </el-table-column>
+          <el-table-column label="照片" width="150">
+            <template slot-scope="s">
+              <el-image
+                v-for="(p, i) in pictures(s.row)"
+                :key="i"
+                :src="p"
+                fit="cover"
+                :preview-src-list="pictures(s.row)"
+                class="thumb" />
+              <span v-if="!pictures(s.row).length" class="muted">-</span>
+            </template>
+          </el-table-column>
+          <el-table-column prop="rmk" label="备注" min-width="120">
+            <template slot-scope="s">{{ s.row.rmk || '-' }}</template>
+          </el-table-column>
+        </el-table>
+      </div>
+
+      <el-alert v-if="task && task.passFlag !== 1" type="warning" :closable="false" class="rect-tip"
+        title="该任务存在不及格项,提交时已自动生成整改记录,请到「整改流程管理」中跟进。" />
+    </div>
+    <span slot="footer">
+      <el-button @click="dialogVisible = false">关闭</el-button>
+    </span>
+  </el-dialog>
+</template>
+
+<script>
+import { post, fmt, fmtNum, splitPictures } from '../common'
+
+export default {
+  name: 'InspectTaskReport',
+  props: {
+    visible: Boolean,
+    taskId: [Number, String]
+  },
+  computed: {
+    dialogVisible: {
+      get() { return this.visible },
+      set(v) { this.$emit('update:visible', v) }
+    }
+  },
+  data() {
+    return { loading: false, task: null, items: [] }
+  },
+  methods: {
+    load() {
+      if (!this.taskId) return
+      this.loading = true
+      Promise.all([
+        post(this.$axios, '/inspectManage/task/detail', { id: this.taskId }),
+        post(this.$axios, '/inspectManage/task/items', { taskId: this.taskId })
+      ]).then(([r1, r2]) => {
+        this.task = (r1 && r1.obj) || null
+        this.items = (r2 && r2.list) || []
+        this.$nextTick(() => {
+          setTimeout(() => this.drawRadar(), 60)
+        })
+      }).finally(() => { this.loading = false })
+    },
+    barWidth(item) {
+      const max = Number(item.maxScore || 0)
+      if (max <= 0) return 0
+      return Math.max(0, Math.min(100, Math.round(Number(item.score || 0) / max * 100)))
+    },
+    pictures(row) { return splitPictures(row.pictureUrls) },
+    drawRadar() {
+      const canvas = this.$refs.radar
+      if (!canvas) return
+      const items = this.items
+      if (!items.length) return
+      const ctx = canvas.getContext('2d')
+      const W = canvas.width
+      const H = canvas.height
+      ctx.clearRect(0, 0, W, H)
+      const cx = W / 2
+      const cy = H / 2
+      const R = Math.min(W, H) / 2 - 44
+      const n = items.length
+      const angle = i => (-Math.PI / 2) + (i * 2 * Math.PI) / n
+      const point = (i, ratio) => ({
+        x: cx + R * ratio * Math.cos(angle(i)),
+        y: cy + R * ratio * Math.sin(angle(i))
+      })
+      // 网格环
+      ctx.lineWidth = 1
+      for (let g = 1; g <= 4; g++) {
+        ctx.beginPath()
+        for (let i = 0; i <= n; i++) {
+          const p = point(i % n, g / 4)
+          if (i === 0) ctx.moveTo(p.x, p.y)
+          else ctx.lineTo(p.x, p.y)
+        }
+        ctx.strokeStyle = 'rgba(0,0,0,0.08)'
+        ctx.stroke()
+      }
+      // 轴线 + 标签
+      ctx.font = '12px Microsoft YaHei, sans-serif'
+      ctx.fillStyle = '#606266'
+      ctx.textAlign = 'center'
+      ctx.textBaseline = 'middle'
+      for (let i = 0; i < n; i++) {
+        const p = point(i, 1)
+        ctx.beginPath()
+        ctx.moveTo(cx, cy)
+        ctx.lineTo(p.x, p.y)
+        ctx.strokeStyle = 'rgba(0,0,0,0.08)'
+        ctx.stroke()
+        const lp = point(i, 1.18)
+        ctx.fillText(items[i].indicatorName, lp.x, lp.y)
+      }
+      // 数据多边形
+      ctx.beginPath()
+      items.forEach((item, i) => {
+        const max = Number(item.maxScore) || 1
+        const ratio = Math.max(0, Math.min(1, (Number(item.score) || 0) / max))
+        const p = point(i, ratio)
+        if (i === 0) ctx.moveTo(p.x, p.y)
+        else ctx.lineTo(p.x, p.y)
+      })
+      ctx.closePath()
+      ctx.fillStyle = 'rgba(24,144,255,0.22)'
+      ctx.fill()
+      ctx.strokeStyle = '#1890ff'
+      ctx.lineWidth = 2
+      ctx.stroke()
+      // 得分点
+      items.forEach((item, i) => {
+        const max = Number(item.maxScore) || 1
+        const ratio = Math.max(0, Math.min(1, (Number(item.score) || 0) / max))
+        const p = point(i, ratio)
+        ctx.beginPath()
+        ctx.arc(p.x, p.y, 3.5, 0, Math.PI * 2)
+        ctx.fillStyle = item.passFlag === 1 ? '#67C23A' : '#F56C6C'
+        ctx.fill()
+      })
+    },
+    fmt,
+    fmtNum
+  }
+}
+</script>
+
+<style scoped>
+.report-wrap { max-height: 560px; overflow-y: auto; }
+.report-head { background: #f5f7fa; border-radius: 4px; padding: 14px 18px; margin-bottom: 14px; display: flex; align-items: center; gap: 20px; }
+.rh-left { flex: 1; }
+.rh-title { font-size: 18px; font-weight: 600; margin-bottom: 6px; }
+.rh-meta { font-size: 13px; color: #909399; }
+.rh-score { text-align: center; }
+.rh-num { font-size: 34px; font-weight: 700; color: #67c23a; }
+.rh-num.fail { color: #f56c6c; }
+.rh-unit { display: block; font-size: 12px; color: #909399; }
+.rh-tag { font-size: 14px; padding: 6px 16px; }
+.report-grid { display: flex; gap: 16px; margin-bottom: 16px; }
+.chart-card { flex: 1; border: 1px solid #ebeef5; border-radius: 6px; padding: 14px; }
+.chart-card h4 { margin: 0 0 10px; font-size: 13px; color: #606266; }
+.radar-canvas { width: 100%; height: auto; display: block; }
+.legend-line { margin-top: 8px; font-size: 12px; color: #909399; display: flex; align-items: center; gap: 12px; }
+.legend-dot { display: inline-block; width: 10px; height: 10px; border-radius: 50%; margin-right: 4px; }
+.legend-dot.ok { background: #67c23a; }
+.legend-dot.fail { background: #f56c6c; }
+.ind-card { border: 1px solid #f0f2f5; border-radius: 4px; padding: 10px 12px; margin-bottom: 8px; border-left: 3px solid #67c23a; }
+.ind-card.ind-fail { border-left-color: #f56c6c; }
+.ind-head { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
+.ind-name { font-weight: 600; }
+.ind-raw { color: #909399; font-size: 12px; margin-left: auto; }
+.ind-body { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
+.score-bar { display: flex; align-items: center; gap: 6px; flex: 1; min-width: 120px; }
+.score-track { flex: 1; height: 8px; background: #f0f2f5; border-radius: 4px; overflow: hidden; }
+.score-fill { height: 100%; border-radius: 4px; }
+.fill-ok { background: #67c23a; }
+.fill-fail { background: #f56c6c; }
+.score-val { font-size: 12px; color: #606266; min-width: 50px; text-align: right; }
+.ind-photo { display: flex; gap: 4px; }
+.thumb-sm { width: 32px; height: 32px; border-radius: 4px; }
+.detail-block { margin-bottom: 4px; }
+.detail-block h4 { margin: 0 0 10px; font-size: 13px; color: #606266; }
+.row-fail { color: #f56c6c; }
+.thumb { width: 40px; height: 40px; margin-right: 6px; border-radius: 4px; }
+.muted { color: #909399; }
+.rect-tip { margin-top: 10px; }
+</style>

+ 133 - 102
src/router/index.js

@@ -1,4 +1,4 @@
-import Vue from "vue";
+import Vue from "vue";
 import Router from "vue-router";
 
 Vue.use(Router);
@@ -38,11 +38,41 @@ export default new Router({
                         require(["@/components/page/admin/Index.vue"], resolve),
                     meta: { titile: "首页" }
                 },
+                {
+                    path: "/admin/inspect",
+                    component: resolve =>
+                        require(["@/components/page/admin/inspect/indicator/Index.vue"], resolve),
+                    meta: { titile: "考核指标配置" }
+                },
+                {
+                    path: "/admin/inspect-plan",
+                    component: resolve =>
+                        require(["@/components/page/admin/inspect/plan/Index.vue"], resolve),
+                    meta: { titile: "巡检计划管理" }
+                },
+                {
+                    path: "/admin/inspect-task",
+                    component: resolve =>
+                        require(["@/components/page/admin/inspect/task/Index.vue"], resolve),
+                    meta: { titile: "巡检任务执行" }
+                },
+                {
+                    path: "/admin/inspect-rect",
+                    component: resolve =>
+                        require(["@/components/page/admin/inspect/rectification/Index.vue"], resolve),
+                    meta: { titile: "整改流程管理" }
+                },
+                {
+                    path: "/admin/inspect-assess",
+                    component: resolve =>
+                        require(["@/components/page/admin/inspect/assess/Index.vue"], resolve),
+                    meta: { titile: "考核结果排名" }
+                },
                 {
                   path: "/admin/updatePassword",
                   component: resolve =>
                       require(["@/components/page/admin/adminManage/UpdatePassword.vue"], resolve),
-                  meta: { titile: "修改密码" }
+                  meta: { titile: "淇敼瀵嗙爜" }
                 },
                 {
                     path: "/setting/settings",
@@ -50,7 +80,7 @@ export default new Router({
                         require([
                             "@/components/page/admin/setting/Settings.vue"
                         ], resolve),
-                    meta: { titile: "系统设置" }
+                    meta: { titile: "绯荤粺璁剧疆" }
                 },
                 {
                   path: "/setting/MenuManage",
@@ -58,7 +88,7 @@ export default new Router({
                       require([
                           "@/components/page/admin/setting/MenuManage.vue"
                       ], resolve),
-                  meta: { titile: "菜单管理" }
+                  meta: { titile: "鑿滃崟绠$悊" }
                 },
                 {
                   path: "/setting/EmployeeManage",
@@ -66,7 +96,7 @@ export default new Router({
                       require([
                           "@/components/page/admin/setting/EmployeeManage.vue"
                       ], resolve),
-                  meta: { titile: "员工管理" }
+                  meta: { titile: "鍛樺伐绠$悊" }
                 },
                 {
                   path: "/setting/ThirdUserManage",
@@ -74,7 +104,7 @@ export default new Router({
                       require([
                           "@/components/page/admin/setting/ThirdUserManage.vue"
                       ], resolve),
-                  meta: { titile: "代运营用户管理" }
+                  meta: { titile: "浠h繍钀ョ敤鎴风鐞?" }
                 },
                 {
                   path: "/setting/PayChannelsManage",
@@ -82,7 +112,7 @@ export default new Router({
                       require([
                           "@/components/page/admin/setting/PayChannelsManage.vue"
                       ], resolve),
-                  meta: { titile: "支付通道管理" }
+                  meta: { titile: "鏀粯閫氶亾绠$悊" }
                 },
                 {
                   path: "/setting/RoleManage",
@@ -90,7 +120,7 @@ export default new Router({
                       require([
                           "@/components/page/admin/setting/RoleManage.vue"
                       ], resolve),
-                  meta: { titile: "角色管理" }
+                  meta: { titile: "瑙掕壊绠$悊" }
                 },
                 {
                   path: "/setting/CampusManage",
@@ -98,7 +128,7 @@ export default new Router({
                       require([
                           "@/components/page/admin/setting/CampusManage.vue"
                       ], resolve),
-                  meta: { titile: "校区管理" }
+                  meta: { titile: "鏍″尯绠$悊" }
                 },
                 {
                   path: "/setting/BigCampusManage",
@@ -106,7 +136,7 @@ export default new Router({
                       require([
                           "@/components/page/admin/setting/BigCampusManage.vue"
                       ], resolve),
-                  meta: { titile: "大校区管理" }
+                  meta: { titile: "澶ф牎鍖虹鐞?" }
                 },
                 {
                   path: "/setting/ScheduledManage",
@@ -114,7 +144,7 @@ export default new Router({
                       require([
                           "@/components/page/admin/setting/ScheduledManage.vue"
                       ], resolve),
-                  meta: { titile: "系统定时器管理" }
+                  meta: { titile: "绯荤粺瀹氭椂鍣ㄧ鐞?" }
                 },
                 {
                   path: "/setting/AccessTokenManage",
@@ -122,7 +152,7 @@ export default new Router({
                       require([
                           "@/components/page/admin/setting/AccessTokenManage.vue"
                       ], resolve),
-                  meta: { titile: "AccessToken管理" }
+                  meta: { titile: "AccessToken绠$悊" }
                 },
                 {
                   path: "/setting/storeSortManage",
@@ -130,7 +160,7 @@ export default new Router({
                         require([
                             "@/components/page/admin/setting/StoreSortManage.vue"
                         ], resolve),
-                    meta: { titile: "门店排序管理" }
+                    meta: { titile: "闂ㄥ簵鎺掑簭绠$悊" }
                 },
 	            {
                   path: "/setting/printerManage",
@@ -138,7 +168,7 @@ export default new Router({
                         require([
                             "@/components/page/admin/setting/PrinterManage.vue"
                         ], resolve),
-                    meta: { titile: "小票机管理" }
+                    meta: { titile: "灏忕エ鏈虹鐞?" }
                 },
 	            {
                   path: "/setting/appVersion",
@@ -146,7 +176,7 @@ export default new Router({
                         require([
                             "@/components/page/admin/setting/AppVersion.vue"
                         ], resolve),
-                    meta: { titile: "app版本管理" }
+                    meta: { titile: "app鐗堟湰绠$悊" }
                 },
                 {
                   path: "/order/orderManage",
@@ -154,7 +184,7 @@ export default new Router({
                         require([
                             "@/components/page/admin/order/OrderManage1.vue"
                         ], resolve),
-                    meta: { titile: "订单处理" }
+                    meta: { titile: "璁㈠崟澶勭悊" }
                 },
                 {
                   path: "/order/orderList",
@@ -162,7 +192,7 @@ export default new Router({
                         require([
                             "@/components/page/admin/order/OrderList.vue"
                         ], resolve),
-                    meta: { titile: "订单列表" }
+                    meta: { titile: "璁㈠崟鍒楄〃" }
                 },
                 {
                   path: "/order/orderInfoDetail",
@@ -170,7 +200,7 @@ export default new Router({
                         require([
                             "@/components/page/admin/order/OrderInfoDetail.vue"
                         ], resolve),
-                    meta: { titile: "订单收入明细" }
+                    meta: { titile: "璁㈠崟鏀跺叆鏄庣粏" }
                 },
                 {
                   path: "/order/refundManage",
@@ -178,7 +208,7 @@ export default new Router({
                         require([
                             "@/components/page/admin/order/RefundManage.vue"
                         ], resolve),
-                    meta: { titile: "退款记录" }
+                    meta: { titile: "閫€娆捐褰?" }
                 },
                 {
                   path: "/order/refundFailed",
@@ -186,7 +216,7 @@ export default new Router({
                         require([
                             "@/components/page/admin/order/RefundFailed.vue"
                         ], resolve),
-                    meta: { titile: "订单收入明细" }
+                    meta: { titile: "璁㈠崟鏀跺叆鏄庣粏" }
                 },
                 {
                   path: "/delivery/deliveryOrderManage",
@@ -194,7 +224,7 @@ export default new Router({
                         require([
                             "@/components/page/admin/delivery/DeliveryOrderManage.vue"
                         ], resolve),
-                    meta: { titile: "配送订单管理" }
+                    meta: { titile: "閰嶉€佽鍗曠鐞?" }
                 },
                 {
                   path: "/delivery/deliveryOrderManage1",
@@ -202,7 +232,7 @@ export default new Router({
                         require([
                             "@/components/page/admin/delivery/DeliveryOrderManage1.vue"
                         ], resolve),
-                    meta: { titile: "新配送订单管理" }
+                    meta: { titile: "鏂伴厤閫佽鍗曠鐞?" }
                 },
                 {
                   path: "/delivery/deliveryerManage",
@@ -210,7 +240,7 @@ export default new Router({
                         require([
                             "@/components/page/admin/delivery/DeliveryerManage.vue"
                         ], resolve),
-                    meta: { titile: "配送员管理" }
+                    meta: { titile: "閰嶉€佸憳绠$悊" }
                 },
                 {
                   path: "/delivery/deliveryerManage1",
@@ -218,7 +248,7 @@ export default new Router({
                         require([
                             "@/components/page/admin/delivery/DeliveryerManage1.vue"
                         ], resolve),
-                    meta: { titile: "新配送员管理" }
+                    meta: { titile: "鏂伴厤閫佸憳绠$悊" }
                 },
                 {
                   path: "/delivery/deliveryAreaManage",
@@ -226,7 +256,7 @@ export default new Router({
                         require([
                             "@/components/page/admin/delivery/DeliveryAreaManage.vue"
                         ], resolve),
-                    meta: { titile: "配送区域管理" }
+                    meta: { titile: "閰嶉€佸尯鍩熺鐞?" }
                 },
                 {
                   path: "/delivery/deliverySettlement",
@@ -234,7 +264,7 @@ export default new Router({
                         require([
                             "@/components/page/admin/delivery/DeliverySettlement.vue"
                         ], resolve),
-                    meta: { titile: "配送员结算单" }
+                    meta: { titile: "閰嶉€佸憳缁撶畻鍗?" }
                 },
                 {
                   path: "/delivery/deliveryAreaGroupManage",
@@ -242,7 +272,7 @@ export default new Router({
                         require([
                             "@/components/page/admin/delivery/DeliveryAreaGroupManage.vue"
                         ], resolve),
-                    meta: { titile: "配送区域分组管理" }
+                    meta: { titile: "閰嶉€佸尯鍩熷垎缁勭鐞?" }
                 },
                 {
                   path: "/delivery/deliveryOrderCount",
@@ -250,7 +280,7 @@ export default new Router({
                         require([
                             "@/components/page/admin/delivery/DeliveryOrderCount.vue"
                         ], resolve),
-                    meta: { titile: "配送员订单统计" }
+                    meta: { titile: "閰嶉€佸憳璁㈠崟缁熻" }
                 },
                 {
                   path: "/delivery/orderGroupLog",
@@ -258,7 +288,7 @@ export default new Router({
                         require([
                             "@/components/page/admin/delivery/OrderGroupLog.vue"
                         ], resolve),
-                    meta: { titile: "订单分组日志" }
+                    meta: { titile: "璁㈠崟鍒嗙粍鏃ュ織" }
                 },
                 {
                   path: "/admin/storemanage",
@@ -266,7 +296,7 @@ export default new Router({
                     require([
                       "@/components/page/admin/store/StoreManage.vue"
                     ], resolve),
-                  meta: { title: "门店列表" }
+                  meta: { title: "闂ㄥ簵鍒楄〃" }
                 },
                 {
                   path: "/admin/storemanage1",
@@ -274,7 +304,7 @@ export default new Router({
                     require([
                       "@/components/page/admin/store/StoreManage.vue"
                     ], resolve),
-                  meta: { title: "新门店列表" }
+                  meta: { title: "鏂伴棬搴楀垪琛?" }
                 },
                 {
                   path: "/admin/storeusermanage",
@@ -282,7 +312,7 @@ export default new Router({
                     require([
                       "@/components/page/admin/store/StoreUserManage.vue"
                     ], resolve),
-                  meta: { title: "门店员工列表" }
+                  meta: { title: "闂ㄥ簵鍛樺伐鍒楄〃" }
                 },
                 {
                   path: "/admin/moreGoodsList",
@@ -290,7 +320,7 @@ export default new Router({
                     require([
                       "@/components/page/admin/store/MoreGoodsList.vue"
                     ], resolve),
-                  meta: { title: "商品列表" }
+                  meta: { title: "鍟嗗搧鍒楄〃" }
                 },
                 {
                   path: "/admin/storeTypeManage",
@@ -298,7 +328,7 @@ export default new Router({
                     require([
                       "@/components/page/admin/store/StoreTypeManage.vue"
                     ], resolve),
-                  meta: { title: "分类管理" }
+                  meta: { title: "鍒嗙被绠$悊" }
                 },
                 {
                   path: "/admin/storeAreaManage",
@@ -306,7 +336,7 @@ export default new Router({
                     require([
                       "@/components/page/admin/store/StoreAreaManage.vue"
                     ], resolve),
-                  meta: { title: "店铺区域管理" }
+                  meta: { title: "搴楅摵鍖哄煙绠$悊" }
                 },
                 {
                   path: "/admin/storeEvaluateManage",
@@ -314,7 +344,7 @@ export default new Router({
                     require([
                       "@/components/page/admin/store/StoreEvaluateManage.vue"
                     ], resolve),
-                  meta: { title: "评价管理" }
+                  meta: { title: "璇勪环绠$悊" }
                 },
                 {
                   path: "/admin/userManage",
@@ -322,7 +352,7 @@ export default new Router({
                     require([
                       "@/components/page/admin/user/UserManage.vue"
                     ], resolve),
-                  meta: { title: "小程序用户" }
+                  meta: { title: "灏忕▼搴忕敤鎴?" }
                 },
                 {
                   path: "/admin/specialsManage",
@@ -330,7 +360,7 @@ export default new Router({
                     require([
                       "@/components/page/admin/operate/SpecialsManage.vue"
                     ], resolve),
-                  meta: { title: "特价菜管理" }
+                  meta: { title: "鐗逛环鑿滅鐞?" }
                 },
                 {
                   path: "/admin/recommendStoreManage",
@@ -338,7 +368,7 @@ export default new Router({
                     require([
                       "@/components/page/admin/operate/RecommendStoreManage.vue"
                     ], resolve),
-                  meta: { title: "首页店铺推荐管理" }
+                  meta: { title: "棣栭〉搴楅摵鎺ㄨ崘绠$悊" }
                 },
                 {
                   path: "/admin/publicAccountParameterManage",
@@ -346,7 +376,7 @@ export default new Router({
                     require([
                       "@/components/page/admin/operate/PublicAccountParameterManage.vue"
                     ], resolve),
-                  meta: { title: "公众号参数二维码" }
+                  meta: { title: "鍏紬鍙峰弬鏁颁簩缁寸爜" }
                 },
                 {
                   path: "/admin/indexIconManage",
@@ -354,7 +384,7 @@ export default new Router({
                     require([
                       "@/components/page/admin/operate/IndexIconManage.vue"
                     ], resolve),
-                  meta: { title: "首页图标管理" }
+                  meta: { title: "棣栭〉鍥炬爣绠$悊" }
                 },
                 {
                   path: "/admin/recommendedLocationManage",
@@ -362,7 +392,7 @@ export default new Router({
                     require([
                       "@/components/page/admin/operate/RecommendedLocationManage.vue"
                     ], resolve),
-                  meta: { title: "首页图标管理" }
+                  meta: { title: "棣栭〉鍥炬爣绠$悊" }
                 },
                 {
                   path: "/admin/carouselManage",
@@ -370,7 +400,7 @@ export default new Router({
                     require([
                       "@/components/page/admin/operate/CarouselManage.vue"
                     ], resolve),
-                  meta: { title: "轮播图管理" }
+                  meta: { title: "杞挱鍥剧鐞?" }
                 },
                 {
                   path: "/admin/announcementManage",
@@ -378,7 +408,7 @@ export default new Router({
                     require([
                       "@/components/page/admin/operate/AnnouncementManage.vue"
                     ], resolve),
-                  meta: { title: "小程序公告" }
+                  meta: { title: "灏忕▼搴忓叕鍛?" }
                 },
                 {
                   path: "/admin/advertiseManage",
@@ -386,7 +416,7 @@ export default new Router({
                     require([
                       "@/components/page/admin/operate/AdvertiseManage.vue"
                     ], resolve),
-                  meta: { title: "广告费维护" }
+                  meta: { title: "骞垮憡璐圭淮鎶?" }
                 },
 		            {
 			            path: "/admin/coupon",
@@ -394,7 +424,7 @@ export default new Router({
 				            require([
 					            "@/components/page/admin/coupon/couponManage.vue"
 				            ], resolve),
-			            meta: { titile: "优惠券管理" }
+			            meta: { titile: "浼樻儬鍒哥鐞?" }
 		            },
 	            {
 			            path: "/admin/couponMember",
@@ -402,7 +432,7 @@ export default new Router({
 				            require([
 					            "@/components/page/admin/coupon/couponMember.vue"
 				            ], resolve),
-			            meta: { titile: "优惠券管理" }
+			            meta: { titile: "浼樻儬鍒哥鐞?" }
 		            },
                 {
                   path: "/admin/advetiseSum",
@@ -410,7 +440,7 @@ export default new Router({
                     require([
                       "@/components/page/admin/operate/AdvetiseSum.vue"
                     ], resolve),
-                  meta: { title: "广告费统计" }
+                  meta: { title: "骞垮憡璐圭粺璁?" }
                 },
                 {
                   path: "/admin/storeAnnouncementManage",
@@ -418,7 +448,7 @@ export default new Router({
                     require([
                       "@/components/page/admin/operate/StoreAnnouncementManage.vue"
                     ], resolve),
-                  meta: { title: "商户公告" }
+                  meta: { title: "鍟嗘埛鍏憡" }
                 },
                 {
                   path: "/admin/dineAndDashManage",
@@ -426,7 +456,7 @@ export default new Router({
                     require([
                       "@/components/page/admin/operate/DineAndDashManage.vue"
                     ], resolve),
-                  meta: { titile: "霸王餐活动" }
+                  meta: { titile: "闇哥帇椁愭椿鍔?" }
                 },
                 {
                   path: "/admin/couponManage",
@@ -434,7 +464,7 @@ export default new Router({
                     require([
                       "@/components/page/admin/operate/CouponManage.vue"
                     ], resolve),
-                  meta: { title: "优惠券" }
+                  meta: { title: "浼樻儬鍒?" }
                 },
                 {
                   path: "/admin/freeMealPowerManage",
@@ -442,7 +472,7 @@ export default new Router({
                     require([
                       "@/components/page/admin/operate/FreeMealPowerManage.vue"
                     ], resolve),
-                  meta: { title: "免餐助力" }
+                  meta: { title: "鍏嶉鍔╁姏" }
                 },
                 {
                   path: "/admin/couponActivityManage",
@@ -450,7 +480,7 @@ export default new Router({
                     require([
                       "@/components/page/admin/operate/CouponActivityManage.vue"
                     ], resolve),
-                  meta: { title: "购券活动" }
+                  meta: { title: "璐埜娲诲姩" }
                 },
                   {
                   path: "/settlement/settlementOrderManage",
@@ -458,7 +488,7 @@ export default new Router({
                     require([
                       "@/components/page/admin/settlement/SettlementOrderManage.vue"
                     ], resolve),
-                  meta: { titile: "结算订单列表" }
+                  meta: { titile: "缁撶畻璁㈠崟鍒楄〃" }
                   },
                   {
                   path: "/settlement/storeSettlementManage",
@@ -466,7 +496,7 @@ export default new Router({
                     require([
                       "@/components/page/admin/settlement/StoreSettlementManage.vue"
                     ], resolve),
-                  meta: { titile: "商家结算单" }
+                  meta: { titile: "鍟嗗缁撶畻鍗?" }
                   },
                   {
                   path: "/settlement/OperatorSettlementManage",
@@ -474,7 +504,7 @@ export default new Router({
                     require([
                       "@/components/page/admin/settlement/OperatorSettlementManage.vue"
                     ], resolve),
-                  meta: { titile: "商家结算单" }
+                  meta: { titile: "鍟嗗缁撶畻鍗?" }
                 },
                   {
                   path: "/settlement/MarketingRecordsManage",
@@ -482,7 +512,7 @@ export default new Router({
                     require([
                       "@/components/page/admin/settlement/MarketingRecordsManage.vue"
                     ], resolve),
-                  meta: { titile: "营销记录" }
+                  meta: { titile: "钀ラ攢璁板綍" }
                   },
                   {
                   path: "/settlement/StoreSettlementCount",
@@ -490,7 +520,7 @@ export default new Router({
                     require([
                       "@/components/page/admin/settlement/StoreSettlementCount.vue"
                     ], resolve),
-                  meta: { titile: "商家结算统计" }
+                  meta: { titile: "鍟嗗缁撶畻缁熻" }
                   },
                   {
                   path: "/dataStatistics/OrderStatistics",
@@ -498,7 +528,7 @@ export default new Router({
                     require([
                       "@/components/page/admin/dataStatistics/OrderStatistics.vue"
                     ], resolve),
-                  meta: { titile: "订单统计" }
+                  meta: { titile: "璁㈠崟缁熻" }
                   },
                   {
                   path: "/dataStatistics/OperationDataStatistics",
@@ -506,7 +536,7 @@ export default new Router({
                     require([
                       "@/components/page/admin/dataStatistics/OperationDataStatistics.vue"
                     ], resolve),
-                  meta: { titile: "运营数据统计" }
+                  meta: { titile: "杩愯惀鏁版嵁缁熻" }
                   },
                   {
                   path: "/dataStatistics/GoodsStatistics",
@@ -514,7 +544,7 @@ export default new Router({
                     require([
                       "@/components/page/admin/dataStatistics/GoodsStatistics.vue"
                     ], resolve),
-                    meta: { titile: "商品统计" }
+                    meta: { titile: "鍟嗗搧缁熻" }
                   },
                   {
                     path: "/dataStatistics/OrderSourceStatistics",
@@ -522,7 +552,7 @@ export default new Router({
                       require([
                         "@/components/page/admin/dataStatistics/OrderSourceStatistics.vue"
                       ], resolve),
-                      meta: { titile: "订单来源统计" }
+                      meta: { titile: "璁㈠崟鏉ユ簮缁熻" }
                     },
                 {
                   path: "/admin/RiderFeedback",
@@ -530,7 +560,7 @@ export default new Router({
                     require([
                       "@/components/page/admin/feedback/RiderFeedback.vue"
                     ], resolve),
-                  meta: { titile: "骑手意见反馈" }
+                  meta: { titile: "楠戞墜鎰忚鍙嶉" }
                 },
                 {
                   path: "/feedback/Complaint",
@@ -538,7 +568,7 @@ export default new Router({
                     require([
                       "@/components/page/admin/feedback/Complaint.vue"
                     ], resolve),
-                  meta: { titile: "投诉建议" }
+                  meta: { titile: "鎶曡瘔寤鸿" }
                 },
           ]
       },
@@ -546,26 +576,26 @@ export default new Router({
         path: "/third/user",
         component: resolve =>
             require(["@/components/page/third/ThirdUser.vue"], resolve),
-        meta: { titile: "店铺" }
+        meta: { titile: "搴楅摵" }
       },
       {
           path: "/third/home",
           component: resolve =>
               require(["@/components/page/third/Main.vue"], resolve),
-          meta: { titile: "主页面" },
+          meta: { titile: "涓婚〉闈?" },
           children: [
               {
                   path: "/third/index",
                   component: resolve =>
                       require(["@/components/page/third/Index.vue"], resolve),
-                  meta: { titile: "首页" }
+                  meta: { titile: "棣栭〉" }
               },
 
               {
                 path: "/third/updatePassword",
                 component: resolve =>
                     require(["@/components/page/third/adminManage/UpdatePassword.vue"], resolve),
-                meta: { titile: "修改密码" }
+                meta: { titile: "淇敼瀵嗙爜" }
               },
               {
                 path: "/third/orderManage",
@@ -573,7 +603,7 @@ export default new Router({
                       require([
                           "@/components/page/third/order/OrderManage1.vue"
                       ], resolve),
-                  meta: { titile: "订单处理" }
+                  meta: { titile: "璁㈠崟澶勭悊" }
               },
               {
                 path: "/third/orderList",
@@ -581,7 +611,7 @@ export default new Router({
                       require([
                           "@/components/page/third/order/OrderList.vue"
                       ], resolve),
-                  meta: { titile: "订单列表" }
+                  meta: { titile: "璁㈠崟鍒楄〃" }
               },
               {
                 path: "/third/orderInfoDetail",
@@ -589,7 +619,7 @@ export default new Router({
                       require([
                           "@/components/page/third/order/OrderInfoDetail.vue"
                       ], resolve),
-                  meta: { titile: "订单收入明细" }
+                  meta: { titile: "璁㈠崟鏀跺叆鏄庣粏" }
               },
               {
                 path: "/third/storeInfo",
@@ -597,7 +627,7 @@ export default new Router({
                   require([
                     "@/components/page/third/store/StoreInfo.vue"
                   ], resolve),
-                meta: { title: "门店信息" }
+                meta: { title: "闂ㄥ簵淇℃伅" }
               },
               {
                 path: "/third/storeusermanage",
@@ -605,7 +635,7 @@ export default new Router({
                   require([
                     "@/components/page/third/store/StoreUserManage.vue"
                   ], resolve),
-                meta: { title: "门店员工列表" }
+                meta: { title: "闂ㄥ簵鍛樺伐鍒楄〃" }
               },
               {
                 path: "/third/storeGoodsList",
@@ -613,7 +643,7 @@ export default new Router({
                   require([
                     "@/components/page/third/goods/StoreGoodsList1.vue"
                   ], resolve),
-                meta: { title: "商品列表" }
+                meta: { title: "鍟嗗搧鍒楄〃" }
               },
               {
                 path: "/third/storeTypeManage",
@@ -621,7 +651,7 @@ export default new Router({
                   require([
                     "@/components/page/third/store/StoreTypeManage.vue"
                   ], resolve),
-                meta: { title: "分类管理" }
+                meta: { title: "鍒嗙被绠$悊" }
               },
               {
                 path: "/third/storeAreaManage",
@@ -629,7 +659,7 @@ export default new Router({
                   require([
                     "@/components/page/third/store/StoreAreaManage.vue"
                   ], resolve),
-                meta: { title: "店铺区域管理" }
+                meta: { title: "搴楅摵鍖哄煙绠$悊" }
               },
               {
                 path: "/third/storeEvaluateManage",
@@ -637,7 +667,7 @@ export default new Router({
                   require([
                     "@/components/page/third/store/StoreEvaluateManage.vue"
                   ], resolve),
-                meta: { title: "评价管理" }
+                meta: { title: "璇勪环绠$悊" }
               },
               {
                 path: "/third/userManage",
@@ -645,7 +675,7 @@ export default new Router({
                   require([
                     "@/components/page/third/user/UserManage.vue"
                   ], resolve),
-                meta: { title: "小程序用户" }
+                meta: { title: "灏忕▼搴忕敤鎴?" }
               },
               {
                 path: "/third/storeSpecials",
@@ -653,7 +683,7 @@ export default new Router({
                   require([
                     "@/components/page/third/operate/StoreSpecials1.vue"
                   ], resolve),
-                meta: { title: "特价菜管理" }
+                meta: { title: "鐗逛环鑿滅鐞?" }
               },
               {
                 path: "/third/newCustomer",
@@ -661,7 +691,7 @@ export default new Router({
                   require([
                     "@/components/page/third/operate/NewCustomer.vue"
                   ], resolve),
-                meta: { title: "新客立减" }
+                meta: { title: "鏂板绔嬪噺" }
               },
               {
                 path: "/third/freeDelivery",
@@ -669,7 +699,7 @@ export default new Router({
                   require([
                     "@/components/page/third/operate/FreeDelivery.vue"
                   ], resolve),
-                meta: { title: "免配送费" }
+                meta: { title: "鍏嶉厤閫佽垂" }
               },
               {
                 path: "/third/fullDiscounts",
@@ -677,7 +707,7 @@ export default new Router({
                   require([
                     "@/components/page/third/operate/FullDiscounts.vue"
                   ], resolve),
-                meta: { title: "满减活动" }
+                meta: { title: "婊″噺娲诲姩" }
               },
               {
                 path: "/third/recommendStoreManage",
@@ -685,7 +715,7 @@ export default new Router({
                   require([
                     "@/components/page/third/operate/RecommendStoreManage.vue"
                   ], resolve),
-                meta: { title: "首页店铺推荐管理" }
+                meta: { title: "棣栭〉搴楅摵鎺ㄨ崘绠$悊" }
               },
               {
                 path: "/third/publicAccountParameterManage",
@@ -693,7 +723,7 @@ export default new Router({
                   require([
                     "@/components/page/third/operate/PublicAccountParameterManage.vue"
                   ], resolve),
-                meta: { title: "公众号参数二维码" }
+                meta: { title: "鍏紬鍙峰弬鏁颁簩缁寸爜" }
               },
               {
                 path: "/third/indexIconManage",
@@ -701,7 +731,7 @@ export default new Router({
                   require([
                     "@/components/page/third/operate/IndexIconManage.vue"
                   ], resolve),
-                meta: { title: "首页图标管理" }
+                meta: { title: "棣栭〉鍥炬爣绠$悊" }
               },
               {
                 path: "/third/recommendedLocationManage",
@@ -709,7 +739,7 @@ export default new Router({
                   require([
                     "@/components/page/third/operate/RecommendedLocationManage.vue"
                   ], resolve),
-                meta: { title: "首页图标管理" }
+                meta: { title: "棣栭〉鍥炬爣绠$悊" }
               },
               {
                 path: "/third/carouselManage",
@@ -717,7 +747,7 @@ export default new Router({
                   require([
                     "@/components/page/third/operate/CarouselManage.vue"
                   ], resolve),
-                meta: { title: "轮播图管理" }
+                meta: { title: "杞挱鍥剧鐞?" }
               },
               {
                 path: "/third/announcementManage",
@@ -725,7 +755,7 @@ export default new Router({
                   require([
                     "@/components/page/third/operate/AnnouncementManage.vue"
                   ], resolve),
-                meta: { title: "小程序公告" }
+                meta: { title: "灏忕▼搴忓叕鍛?" }
               },
               {
                 path: "/third/storeAnnouncementManage",
@@ -733,7 +763,7 @@ export default new Router({
                   require([
                     "@/components/page/third/operate/StoreAnnouncementManage.vue"
                   ], resolve),
-                meta: { title: "商户公告" }
+                meta: { title: "鍟嗘埛鍏憡" }
               },
               {
                 path: "/third/dineAndDashManage",
@@ -741,7 +771,7 @@ export default new Router({
                   require([
                     "@/components/page/third/operate/DineAndDashManage.vue"
                   ], resolve),
-                meta: { titile: "霸王餐活动" }
+                meta: { titile: "闇哥帇椁愭椿鍔?" }
               },
               {
                 path: "/third/freeMealPowerManage",
@@ -749,7 +779,7 @@ export default new Router({
                   require([
                     "@/components/page/third/operate/FreeMealPowerManage.vue"
                   ], resolve),
-                meta: { title: "免餐助力" }
+                meta: { title: "鍏嶉鍔╁姏" }
               },
               {
                 path: "/third/couponActivityManage",
@@ -757,7 +787,7 @@ export default new Router({
                   require([
                     "@/components/page/third/operate/CouponActivityManage.vue"
                   ], resolve),
-                meta: { title: "购券活动" }
+                meta: { title: "璐埜娲诲姩" }
               },
                 {
                 path: "/third/settlement/settlementOrderManage",
@@ -765,7 +795,7 @@ export default new Router({
                   require([
                     "@/components/page/third/settlement/SettlementOrderManage.vue"
                   ], resolve),
-                meta: { titile: "结算订单列表" }
+                meta: { titile: "缁撶畻璁㈠崟鍒楄〃" }
                 },
                 {
                 path: "/third/settlement/storeSettlementManage",
@@ -773,7 +803,7 @@ export default new Router({
                   require([
                     "@/components/page/third/settlement/StoreSettlementManage.vue"
                   ], resolve),
-                meta: { titile: "商家结算单" }
+                meta: { titile: "鍟嗗缁撶畻鍗?" }
                 },
                 {
                 path: "/settlement/OperatorSettlementManage",
@@ -781,7 +811,7 @@ export default new Router({
                   require([
                     "@/components/page/third/settlement/OperatorSettlementManage.vue"
                   ], resolve),
-                meta: { titile: "商家结算单" }
+                meta: { titile: "鍟嗗缁撶畻鍗?" }
               },
                 {
                 path: "/settlement/MarketingRecordsManage",
@@ -789,7 +819,7 @@ export default new Router({
                   require([
                     "@/components/page/third/settlement/MarketingRecordsManage.vue"
                   ], resolve),
-                meta: { titile: "营销记录" }
+                meta: { titile: "钀ラ攢璁板綍" }
                 },
                 {
                 path: "/settlement/StoreSettlementCount",
@@ -797,7 +827,7 @@ export default new Router({
                   require([
                     "@/components/page/third/settlement/StoreSettlementCount.vue"
                   ], resolve),
-                meta: { titile: "商家结算统计" }
+                meta: { titile: "鍟嗗缁撶畻缁熻" }
                 },
                 {
                 path: "/third/dataStatistics/OrderStatistics",
@@ -805,7 +835,7 @@ export default new Router({
                   require([
                     "@/components/page/third/dataStatistics/OrderStatistics.vue"
                   ], resolve),
-                meta: { titile: "订单统计" }
+                meta: { titile: "璁㈠崟缁熻" }
                 },
                 {
                 path: "/dataStatistics/OperationDataStatistics",
@@ -813,7 +843,7 @@ export default new Router({
                   require([
                     "@/components/page/third/dataStatistics/OperationDataStatistics.vue"
                   ], resolve),
-                meta: { titile: "运营数据统计" }
+                meta: { titile: "杩愯惀鏁版嵁缁熻" }
                 },
                 {
                 path: "/third/dataStatistics/GoodsStatistics",
@@ -821,7 +851,7 @@ export default new Router({
                   require([
                     "@/components/page/third/dataStatistics/GoodsStatistics.vue"
                   ], resolve),
-                  meta: { titile: "商品统计" }
+                  meta: { titile: "鍟嗗搧缁熻" }
                 },
                 {
                   path: "/dataStatistics/OrderSourceStatistics",
@@ -829,7 +859,7 @@ export default new Router({
                     require([
                       "@/components/page/third/dataStatistics/OrderSourceStatistics.vue"
                     ], resolve),
-                    meta: { titile: "订单来源统计" }
+                    meta: { titile: "璁㈠崟鏉ユ簮缁熻" }
                   },
               {
                 path: "/store/deliveryManage",
@@ -837,7 +867,7 @@ export default new Router({
                 require([
                   "@/components/page/third/store/DeliveryManage.vue"
                 ], resolve),
-                meta: { titile: "配送管理" }
+                meta: { titile: "閰嶉€佺鐞?" }
               }
           ]
       },
@@ -856,3 +886,4 @@ export default new Router({
       }
     ]
 });
+