dispatch_center.html 48 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257
  1. <!DOCTYPE html>
  2. <html lang="zh-CN">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>AI调度管理</title>
  6. <link rel="stylesheet" href="/static/elementui2.15.5/index.css"/>
  7. <link rel="stylesheet" href="/static/ai/css/dispatch_center.css"/>
  8. <script src="/static/hplus/js/jquery.min.js?v=2.1.4"></script>
  9. <script src="/static/layer3.2.0/layer.js"></script>
  10. <script src="/static/elementui2.15.5/vue-2.6.14.js"></script>
  11. <script src="/static/elementui2.15.5/index.js"></script>
  12. <script src="/static/echarts.js"></script>
  13. </head>
  14. <body>
  15. <div class="page-box">
  16. <!-- 顶部导航 -->
  17. <div class="top-nav">
  18. <div
  19. class="top-item active"
  20. data-type="ai">
  21. AI调度中心
  22. </div>
  23. <div
  24. class="top-item "
  25. data-type="dispatch">
  26. 派单列表
  27. </div>
  28. <div
  29. class="top-item"
  30. data-type="setting">
  31. 派单设置
  32. </div>
  33. </div>
  34. <!-- 派单列表 / 派单设置 -->
  35. <div
  36. class="main"
  37. id="mainContainer"
  38. style="display:none;">
  39. <div class="sidebar">
  40. </div>
  41. <div class="content">
  42. <!-- Loading -->
  43. <div id="pageLoading">
  44. <div class="loading-box">
  45. <div class="loading-spinner"></div>
  46. <div class="loading-text">
  47. 一大批数据正在赶来......
  48. </div>
  49. </div>
  50. </div>
  51. <iframe id="contentFrame" src="">
  52. </iframe>
  53. </div>
  54. </div>
  55. <!-- AI调度中心 -->
  56. <div id="aiContainer">
  57. <!-- <el-card class="main-card">-->
  58. <!-- 顶部 -->
  59. <el-row :gutter="10" class="top-area">
  60. <!-- 左侧整体 -->
  61. <el-col :lg="18" :md="24">
  62. <!-- 上部分 -->
  63. <el-row :gutter="10">
  64. <!-- 左侧统计 -->
  65. <el-col :span="8">
  66. <div class="stat-wrap" v-loading="loading" element-loading-text="数据加载中......">
  67. <div class="big-card" style='background-image: url("/static/ai/img/dispatched.png")' @click="handleModeChange(0)">
  68. <div class="big-left">
  69. <div class="big-title">AI派单总数</div>
  70. <div class="big-value">{{done.total}}</div>
  71. </div>
  72. </div>
  73. <div class="small-grid">
  74. <el-row :gutter="8" style="height:100%;">
  75. <el-col :span="12" style="margin-bottom:8px;">
  76. <div class="stat-card" style='background-image: url("/static/ai/img/bj.png")'>
  77. <h3>保洁派单数</h3><p style="color: #0983E2FF">{{done.bj}}</p>
  78. </div>
  79. </el-col>
  80. <el-col :span="12" style="margin-bottom:8px;">
  81. <div class="stat-card" style='background-image: url("/static/ai/img/bx.png")'>
  82. <h3>维修派单数</h3><p style="color: #00905DFF">{{done.bx}}</p>
  83. </div>
  84. </el-col>
  85. <el-col :span="12">
  86. <div class="stat-card" style='background-image: url("/static/ai/img/ys.png")'>
  87. <h3>运送工单数</h3><p style="color: #E68417FF">{{done.ys}}</p>
  88. </div>
  89. </el-col>
  90. <el-col :span="12">
  91. <div class="stat-card" style='background-image: url("/static/ai/img/ab.png")'>
  92. <h3>安保待派单数</h3><p style="color: #8943DDFF">{{done.ab}}</p>
  93. </div>
  94. </el-col>
  95. </el-row>
  96. </div>
  97. </div>
  98. </el-col>
  99. <!-- 中间banner -->
  100. <el-col :span="16">
  101. <div class="center-banner">
  102. <video
  103. class="video"
  104. src="/static/ai/img/ai.mp4"
  105. autoplay
  106. loop
  107. muted
  108. playsinline
  109. webkit-playsinline
  110. x5-playsinline
  111. disablePictureInPicture>
  112. 您的浏览器不支持视频播放
  113. </video>
  114. </div>
  115. </el-col>
  116. </el-row>
  117. </el-col>
  118. <!-- 右侧 -->
  119. <el-col :lg="6" :md="24">
  120. <div class="stat-wrap" v-loading="loading" element-loading-text="数据加载中......">
  121. <div class="big-card" style='background-image: url("/static/ai/img/un-dispatched.png")' @click="handleModeChange2(0)">
  122. <div class="big-left">
  123. <div class="big-title">待派单总数</div>
  124. <div class="big-value">{{undo.total}}</div>
  125. </div>
  126. </div>
  127. <div class="small-grid">
  128. <el-row :gutter="8" style="height:100%;">
  129. <el-col :span="12" style="margin-bottom:8px;">
  130. <div class="stat-card" style='background-image: url("/static/ai/img/bj.png")'>
  131. <h3>保洁待派单数</h3><p style="color: #0983E2FF">{{undo.bj}}</p>
  132. </div>
  133. </el-col>
  134. <el-col :span="12" style="margin-bottom:8px;">
  135. <div class="stat-card" style='background-image: url("/static/ai/img/bx.png")'>
  136. <h3>维修待派单数</h3><p style="color: #00905DFF">{{undo.bx}}</p>
  137. </div>
  138. </el-col>
  139. <el-col :span="12">
  140. <div class="stat-card" style='background-image: url("/static/ai/img/ys.png")'>
  141. <h3>运送待派单数</h3><p style="color: #E68417FF">{{undo.ys}}</p>
  142. </div>
  143. </el-col>
  144. <el-col :span="12">
  145. <div class="stat-card" style='background-image: url("/static/ai/img/ab.png")'>
  146. <h3>安保待派单数</h3><p style="color: #8943DDFF">{{undo.ab}}</p>
  147. </div>
  148. </el-col>
  149. </el-row>
  150. </div>
  151. </div>
  152. </el-col>
  153. </el-row>
  154. <!-- </el-card>-->
  155. <div class="table-header" >
  156. <div
  157. v-for="item in tabs"
  158. :key="item.value"
  159. class="tab-item"
  160. :class="{ active: currentTab === item.value }"
  161. @click="changeTab(item)"
  162. >
  163. <span class="tab-text" :style="{ fontSize: getFontSize(item) }">
  164. {{ item.label }}(
  165. <span :ref="`count_${item.value}`" class="count-number">{{ item.count }}</span>
  166. </span>
  167. </div>
  168. </div>
  169. <!-- 底部表格 -->
  170. <el-card class="table-card" shadow="never">
  171. <el-row style="margin-top:10px;">
  172. <el-col :span="24">
  173. <el-table v-loading="tableLoading"
  174. element-loading-text="一大批数据正在赶来......"
  175. ref="dispatchedTable"
  176. :data="tableData"
  177. style="width:100%;min-height: 50vh">
  178. <el-table-column prop="sn" label="订单编号">
  179. <template slot-scope="scope">
  180. <a href="javascript:;"
  181. data-title="详情"
  182. @click="layerOpen(scope.row.id)"
  183. data-title="详情"
  184. style="color:#0056DBFF;text-decoration: none">
  185. {{ scope.row.sn }}
  186. </a>
  187. </template>
  188. </el-table-column>
  189. <el-table-column prop="type" label="订单类型"></el-table-column>
  190. <el-table-column prop="user" label="发起人"></el-table-column>
  191. <el-table-column v-if="currentTab == 'today'" prop="status" label="订单状态">
  192. <template slot-scope="scope">
  193. <span class="status-dispatch">已派单</span>
  194. </template>
  195. </el-table-column>
  196. <el-table-column v-if="currentTab == 'today'" prop="create_time" label="派单时间"></el-table-column>
  197. <el-table-column v-if="currentTab == 'today'" prop="toUser" label="指派员工"></el-table-column>
  198. <el-table-column v-if="currentTab == 'today'" label="操作">
  199. <template slot-scope="scope">
  200. <a href="javascript:;"
  201. data-title="重新派单"
  202. @click="send(scope.row.todo_id,scope.row.mode,0)"
  203. style="color:#0056DBFF;text-decoration: none">
  204. 重新派单
  205. </a>
  206. </template>
  207. </el-table-column>
  208. <el-table-column v-if="currentTab != 'today'" prop="status" label="状态">
  209. <template slot-scope="scope">
  210. <div v-if="currentTab == 'error'">
  211. <span class="status-fail">派单未匹配</span>
  212. </div>
  213. <div v-else>
  214. <img v-if="scope.row.flag" src="/static/ai/img/wait.png" class="loading-ico">
  215. <span :class="scope.row.flag ? 'status-inprogress':'status-pending'">{{ scope.row.flag && ai == 1 ? 'Ai派单中...':'待派单' }}</span>
  216. </div>
  217. </template>
  218. </el-table-column>
  219. <el-table-column v-if="currentTab == 'error'" prop="fail" label="操作">
  220. <template slot-scope="scope">
  221. <a href="javascript:;"
  222. data-title="手动派单"
  223. @click="send(scope.row.id,scope.row.work_type_mode,1)"
  224. style="color:#0056DBFF;text-decoration: none">
  225. 手动派单
  226. </a>
  227. </template>
  228. </el-table-column>
  229. </el-table>
  230. <!-- 分页 -->
  231. <div class="page" v-if="tableData.length > 0">
  232. <el-pagination
  233. background
  234. layout="prev, pager, next, total"
  235. :total="total"
  236. :page-size="pageSize"
  237. :current-page.sync="currentPage"
  238. @current-change="handlePageChange">
  239. </el-pagination>
  240. </div>
  241. </el-col>
  242. </el-row>
  243. </el-card>
  244. <!-- 员工数据分析-->
  245. <el-card class="dashboard-card">
  246. <el-row :gutter="20" class="dashboard-row">
  247. <!-- 左侧 -->
  248. <el-col :xl="10" :lg="10" :md="24" :sm="24" class="left-col">
  249. <div class="panel" v-loading="staffLoading" element-loading-text="数据加载中......">
  250. <div class="panel-title">
  251. 在岗员工数据
  252. </div>
  253. <div class="staff-wrap">
  254. <div
  255. ref="staffChart"
  256. class="staff-chart">
  257. </div>
  258. <div class="staff-cards">
  259. <div class="staff-card">
  260. <div class="card-label">
  261. <img src="/static/ai/img/bj-icon.png" class="staff-icon">
  262. <span>保洁在岗</span>
  263. </div>
  264. <div class="card-num">
  265. {{bjInfo.active || 0}}
  266. <span>/{{bjInfo.total || 0}}</span>
  267. </div>
  268. </div>
  269. <div class="staff-card">
  270. <div class="card-label">
  271. <img src="/static/ai/img/ys-icon.png" class="staff-icon">
  272. <span>运送在岗</span>
  273. </div>
  274. <div class="card-num">
  275. {{ysInfo.active || 0}}
  276. <span>/{{ysInfo.total || 0}}</span>
  277. </div>
  278. </div>
  279. <div class="staff-card">
  280. <div class="card-label">
  281. <img src="/static/ai/img/bx-icon.png" class="staff-icon">
  282. <span>维修在岗</span>
  283. </div>
  284. <div class="card-num">
  285. {{bxInfo.active || 0}}
  286. <span>/{{bxInfo.total || 0}}</span>
  287. </div>
  288. </div>
  289. <div class="staff-card">
  290. <div class="card-label">
  291. <img src="/static/ai/img/ab-icon.png" class="staff-icon">
  292. <span>安保在岗</span>
  293. </div>
  294. <div class="card-num">
  295. {{abInfo.active || 0}}
  296. <span>/{{abInfo.total || 0}}</span>
  297. </div>
  298. </div>
  299. </div>
  300. </div>
  301. </div>
  302. <div class="panel mt20" v-loading="orderLoading" element-loading-text="数据加载中......">
  303. <div class="panel-header">
  304. <div class="panel-title">
  305. 各类型订单派单分析
  306. </div>
  307. <div class="date-tabs">
  308. <div
  309. v-for="item in chartTabs"
  310. :key="item.value"
  311. class="date-tab"
  312. :class="{ active: orderTab === item.value }"
  313. @click="changeOrderTab(item)"
  314. >
  315. {{item.label}}
  316. </div>
  317. </div>
  318. </div>
  319. <div
  320. ref="orderChart"
  321. class="order-chart">
  322. </div>
  323. </div>
  324. </el-col>
  325. <!-- 右侧 -->
  326. <el-col :xl="14" :lg="14" :md="24" :sm="24" v-loading="userLoading" element-loading-text="一大批数据正在赶来......">
  327. <div class="panel table-panel" ref="user">
  328. <div class="table-header2">
  329. <div class="panel-title">
  330. 员工数据总览
  331. </div>
  332. <div class="role-tabs">
  333. <div
  334. v-for="item in roleTabs"
  335. :key="item.value"
  336. class="role-tab"
  337. :class="{ active: roleTab === item.value }"
  338. @click="changeRoleTab(item)"
  339. >
  340. {{item.label}}
  341. </div>
  342. </div>
  343. </div>
  344. <el-table
  345. :data="userData"
  346. class="staff-table"
  347. ref="userTable"
  348. style="width:100%;">
  349. <el-table-column show-overflow-tooltip label="员工">
  350. <template slot-scope="scope">
  351. <div>
  352. <span>
  353. {{scope.row.real_name}}
  354. </span>
  355. </div>
  356. </template>
  357. </el-table-column>
  358. <el-table-column prop="role_name" label="角色"></el-table-column>
  359. <el-table-column prop="status" label="状态">
  360. <template slot-scope="scope">
  361. <div>
  362. <el-tag
  363. :type="scope.row.work === 1 ? 'success' : 'danger'"
  364. size="small">
  365. {{ scope.row.work === 1 ? '在岗' : '离岗' }}
  366. </el-tag>
  367. </div>
  368. </template>
  369. </el-table-column>
  370. <el-table-column prop="doing" label="进行中"></el-table-column>
  371. <el-table-column prop="wait" label="待处理"></el-table-column>
  372. <el-table-column prop="receive" label="接单平均时长"></el-table-column>
  373. <el-table-column prop="finish" label="任务平均耗时"></el-table-column>
  374. </el-table>
  375. <div class="pagination-wrap" v-if="userData.length > 0">
  376. <el-pagination
  377. background
  378. layout="prev, pager, next, total"
  379. :total="userTotal"
  380. :page-size="7"
  381. :current-page.sync="userCurrentPage"
  382. @current-change="handleUserPageChange">
  383. </el-pagination>
  384. </div>
  385. </div>
  386. </el-col>
  387. </el-row>
  388. </el-card>
  389. <!-- 趋势图 -->
  390. <el-card class="chart-card" v-loading="loading3" element-loading-text="数据加载中......" shadow="never">
  391. <div class="chart-header">
  392. <div class="chart-title">
  393. 派单趋势分析
  394. </div>
  395. <div class="chart-tabs">
  396. <div
  397. v-for="item in chartTabs"
  398. :key="item.value"
  399. class="chart-tab"
  400. :class="{ active: chartTab === item.value }"
  401. @click="changeChartTab(item)"
  402. >
  403. {{item.label}}
  404. </div>
  405. </div>
  406. </div>
  407. <div ref="chart" id="chart"></div>
  408. </el-card>
  409. </div>
  410. </div>
  411. <script>
  412. new Vue({
  413. el:'#aiContainer',
  414. data: function() {
  415. return {
  416. ai:{$ai},
  417. aiList:[0,0,0,0,0,0,0],
  418. userList:[0,0,0,0,0,0,0],
  419. orderAi:[0,0,0,0,],
  420. orderUser:[0,0,0,0],
  421. modes:[],
  422. done: {'total':0,'bx':0, 'bj': 0, 'ys' : 0, 'ab' : 0,},
  423. undo: {'total':0,'bx':0, 'bj': 0, 'ys' : 0, 'ab' : 0},
  424. dateList:[],
  425. currentPage: 1,
  426. pageSize: 10,
  427. total: 0,
  428. mode: 0,
  429. tableData:[],
  430. loading:true,
  431. tableLoading:true,
  432. loading3:true,
  433. userCurrentPage: 1,
  434. userTotal: 0,
  435. chartLoading: true,
  436. staffLoading: true,
  437. orderLoading: true,
  438. userLoading: true,
  439. bxInfo: {},
  440. bjInfo: {},
  441. ysInfo: {},
  442. abInfo: {},
  443. staffData:{},
  444. staffSummary:{},
  445. currentTab: 'today',
  446. tabs: [
  447. {
  448. value: 'today',
  449. label: '今日已派单',
  450. count: 0
  451. },
  452. {
  453. value: 'wait',
  454. label: '待派单',
  455. count: 0
  456. },
  457. {
  458. value: 'error',
  459. label: '异常订单',
  460. count: 0
  461. }
  462. ],
  463. userData: [],
  464. chartTabs: [
  465. {
  466. value: 'day',
  467. label: '近七日',
  468. },
  469. {
  470. value: 'month',
  471. label: '本月',
  472. },
  473. {
  474. value: 'year',
  475. label: '本年',
  476. }
  477. ],
  478. chartTab: 'day',
  479. orderTab: 'day',
  480. roleTabs: [
  481. {
  482. value: 0,
  483. label: '综合',
  484. },
  485. {
  486. value: 2,
  487. label: '保洁',
  488. },
  489. {
  490. value: 3,
  491. label: '运送',
  492. },
  493. {
  494. value: 1,
  495. label: '维修',
  496. },
  497. {
  498. value: 4,
  499. label: '安保',
  500. },
  501. ],
  502. roleTab: 0,
  503. flag1: false,
  504. flag2: false,
  505. flag3: false,
  506. flag4: false,
  507. }
  508. },
  509. mounted(){
  510. this.observe('staffChart', () => {
  511. this.flag1 = true;
  512. this.fetchStaffData();
  513. })
  514. this.observe('orderChart', () => {
  515. this.flag2 = true;
  516. this.fetchOrderData();
  517. })
  518. this.observe('chart', () => {
  519. this.flag3 = true;
  520. this.fetchEchart();
  521. })
  522. this.observe('user', () => {
  523. this.flag4 = true;
  524. this.fetchUserData();
  525. })
  526. this.getData();
  527. setInterval(() => {
  528. this.getData();
  529. if (this.flag1) this.fetchStaffData();
  530. if (this.flag2) this.fetchOrderData();
  531. if (this.flag3) this.fetchEchart();
  532. if (this.flag4) this.fetchUserData();
  533. }, 1000 * 60);
  534. },
  535. methods: {
  536. getData(){
  537. if (this.currentTab === 'today'){
  538. this.fetchDispatchedData();
  539. }else{
  540. this.fetchUnDispatchedData();
  541. }
  542. this.fetchStatistics();
  543. this.fetchCount();
  544. },
  545. fetchStatistics() {
  546. this.loading = true;
  547. const url = "{:url('AiStatistics/statisticsData')}";
  548. $.ajax({
  549. url: url,
  550. type: 'POST',
  551. dataType: 'json',
  552. success: (res) => {
  553. this.done = res.done;
  554. this.undo = res.undo;
  555. this.loading = false;
  556. },
  557. error: function(xhr, status, error) {
  558. this.loading = false;
  559. console.error('请求失败:', error);
  560. }
  561. });
  562. },
  563. fetchEchart() {
  564. this.loading3 = true;
  565. const url = "{:url('AiStatistics/chartData')}";
  566. $.ajax({
  567. url: url,
  568. type: 'POST',
  569. dataType: 'json',
  570. data:{
  571. type:this.chartTab
  572. },
  573. success: (res) => { // 使用箭头函数
  574. this.aiList = res.ai;
  575. this.userList = res.user;
  576. this.date = res.date
  577. this.initEchart();
  578. },
  579. error: function(xhr, status, error) {
  580. console.error('请求失败:', error);
  581. }
  582. });
  583. },
  584. fetchDispatchedData() {
  585. this.tableData = [];
  586. this.tableLoading = true;
  587. const url = "{:url('AiStatistics/dispatchedData')}";
  588. $.ajax({
  589. url: url,
  590. type: 'POST',
  591. dataType: 'json',
  592. data:{
  593. page:this.currentPage,
  594. size:this.pageSize,
  595. mode:this.mode
  596. },
  597. success: (res) => { // 使用箭头函数
  598. this.tableData = res.data;
  599. this.total = res.total;
  600. this.tableLoading = false;
  601. },
  602. error: function(xhr, status, error) {
  603. this.tableLoading = false;
  604. console.error('请求失败:', error);
  605. }
  606. });
  607. },
  608. fetchUnDispatchedData() {
  609. this.tableData = [];
  610. this.tableLoading = true;
  611. const url = "{:url('AiStatistics/unDispatchedData')}";
  612. $.ajax({
  613. url: url,
  614. type: 'POST',
  615. dataType: 'json',
  616. data:{
  617. page:this.currentPage,
  618. size:this.pageSize,
  619. type:this.currentTab,
  620. },
  621. success: (res) => { // 使用箭头函数
  622. this.tableData = res.data;
  623. this.total = res.total;
  624. this.tableLoading = false;
  625. },
  626. error: function(xhr, status, error) {
  627. this.tableLoading = false;
  628. console.error('请求失败:', error);
  629. }
  630. });
  631. },
  632. fetchCount() {
  633. const url = "{:url('AiStatistics/countData')}";
  634. $.ajax({
  635. url: url,
  636. type: 'POST',
  637. dataType: 'json',
  638. success: (res) => { // 使用箭头函数
  639. this.updateCountByValue('today', res.today);
  640. this.updateCountByValue('wait', res.wait);
  641. this.updateCountByValue('error', res.error);
  642. },
  643. error: function(xhr, status, error) {
  644. console.error('请求失败:', error);
  645. }
  646. });
  647. },
  648. fetchStaffData() {
  649. this.staffLoading = true;
  650. const url = "{:url('AiStatistics/staffData')}";
  651. $.ajax({
  652. url: url,
  653. type: 'POST',
  654. dataType: 'json',
  655. success: (res) => { // 使用箭头函数
  656. this.bxInfo = res.bxInfo;
  657. this.bjInfo = res.bjInfo;
  658. this.ysInfo = res.ysInfo;
  659. this.abInfo = res.abInfo;
  660. this.staffData = res.staffData;
  661. this.staffSummary = res.staffSummary;
  662. this.initStaffChart();
  663. },
  664. error: function(xhr, status, error) {
  665. this.staffLoading = false;
  666. console.error('请求失败:', error);
  667. }
  668. });
  669. },
  670. fetchOrderData() {
  671. this.orderLoading = true;
  672. const url = "{:url('AiStatistics/orderData')}";
  673. $.ajax({
  674. url: url,
  675. type: 'POST',
  676. dataType: 'json',
  677. data:{
  678. type:this.orderTab
  679. },
  680. success: (res) => { // 使用箭头函数
  681. this.orderAi = res.ai;
  682. this.orderUser = res.user;
  683. this.modes = res.modes;
  684. this.initOrderChart();
  685. },
  686. error: function(xhr, status, error) {
  687. this.orderLoading = false;
  688. console.error('请求失败:', error);
  689. }
  690. });
  691. },
  692. fetchUserData() {
  693. this.userData = [];
  694. this.userLoading = true;
  695. const url = "{:url('AiStatistics/userData')}";
  696. $.ajax({
  697. url: url,
  698. type: 'POST',
  699. dataType: 'json',
  700. data:{
  701. mode:this.roleTab,
  702. page: this.userCurrentPage
  703. },
  704. success: (res) => { // 使用箭头函数
  705. this.userLoading = false;
  706. this.userData = res.data;
  707. this.userTotal = res.total
  708. },
  709. error: function(xhr, status, error) {
  710. this.userLoading = false;
  711. console.error('请求失败:', error);
  712. }
  713. });
  714. },
  715. handlePageChange(page) {
  716. this.currentPage = page;
  717. if (this.currentTab === 'today'){
  718. this.fetchDispatchedData();
  719. }else{
  720. this.fetchUnDispatchedData();
  721. }
  722. },
  723. handleSizeChange(val) {
  724. this.pageSize = val;
  725. if (this.currentTab === 'today'){
  726. this.fetchDispatchedData();
  727. }else{
  728. this.fetchUnDispatchedData();
  729. }
  730. },
  731. handleModeChange(val) {
  732. layer.open({
  733. type: 2,
  734. title: "历史派单",
  735. shadeClose: true,
  736. shade: 0.3,
  737. maxmin: true,
  738. area: ['90%', '90%'],
  739. content: "{:url('history')}",
  740. end: function () {
  741. // window.location.reload();
  742. }
  743. });
  744. },
  745. handleUserPageChange(page){
  746. this.userCurrentPage = page;
  747. this.fetchUserData()
  748. },
  749. handleModeChange2(val) {
  750. layer.open({
  751. type: 2,
  752. title: "待派订单",
  753. shadeClose: true,
  754. shade: 0.3,
  755. maxmin: true,
  756. area: ['90%', '90%'],
  757. content: "{:url('UnDispatched')}",
  758. end: function () {
  759. // window.location.reload();
  760. }
  761. });
  762. },
  763. changeTab(item) {
  764. if (this.currentTab === item.value) return;
  765. this.currentPage = 1;
  766. this.currentTab = item.value;
  767. this.fetchCount();
  768. if (this.currentTab === 'today'){
  769. this.fetchDispatchedData();
  770. }else{
  771. this.fetchUnDispatchedData();
  772. }
  773. },
  774. changeChartTab(item) {
  775. if (this.chartTab === item.value) return;
  776. this.chartTab = item.value;
  777. this.fetchEchart();
  778. },
  779. changeOrderTab(item) {
  780. if (this.orderTab === item.value) return;
  781. this.orderTab = item.value;
  782. this.fetchOrderData();
  783. },
  784. changeRoleTab(item) {
  785. if (this.roleTab === item.value) return;
  786. this.userCurrentPage = 1;
  787. this.roleTab = item.value;
  788. this.fetchUserData();
  789. },
  790. getFontSize(item){
  791. const text = `${item.label}(${item.count})`;
  792. const len = text.length;
  793. if(len <= 10) return '16px';
  794. if(len <= 15) return '14px';
  795. if(len <= 20) return '12px';
  796. return '10px';
  797. },
  798. initEchart() {
  799. // 左侧折线图
  800. var chart = echarts.init(document.getElementById('chart'));
  801. window.addEventListener('resize', () => {
  802. chart.resize();
  803. });
  804. var option = {
  805. grid:{
  806. left:40,
  807. right:100,
  808. bottom:-5,
  809. containLabel:true
  810. },
  811. tooltip:{trigger:'axis'},
  812. legend:{data:['AI派单','人工派单']},
  813. xAxis:{type:'category', splitLine: {show: false}, data:this.date},
  814. yAxis: {
  815. type: 'value',
  816. splitLine: { show: false },
  817. name: '',
  818. nameTextStyle: {
  819. fontSize: 15,
  820. color: '#333',
  821. }
  822. },
  823. series:[
  824. { name:'AI派单', type:'line',symbolSize: 10, data:this.aiList, smooth:false , itemStyle:{color:'#7000FBFF'}, areaStyle:{color: {
  825. type: 'linear',
  826. x: 0, y: 0,
  827. x2: 0, y2: 1, // 从左到右
  828. colorStops: [
  829. { offset: 0, color: '#eee0ffFF' },
  830. { offset: 1, color: '#7000fb03' }
  831. ]
  832. }}},
  833. { name:'人工派单', type:'line',symbolSize: 10, data:this.userList, smooth:false, itemStyle:{color:'#0062FBFF'},areaStyle: {
  834. color: {
  835. type: 'linear',
  836. x: 0, y: 0,
  837. x2: 0, y2: 1, // 从左到右
  838. colorStops: [
  839. { offset: 0, color: '#a9cbffFF' },
  840. { offset: 1, color: '#ffffff03' }
  841. ]
  842. }
  843. }}
  844. ]
  845. };
  846. chart.setOption(option);
  847. this.loading3 = false;
  848. },
  849. initStaffChart() {
  850. const chart = echarts.init(this.$refs.staffChart)
  851. chart.setOption({
  852. tooltip: {
  853. trigger: 'item'
  854. },
  855. series:[
  856. {
  857. name: '在岗人数',
  858. type:'pie',
  859. radius:['65%','90%'],
  860. avoidLabelOverlap:true,
  861. label:{
  862. show:true,
  863. position:'center',
  864. formatter: `在岗人数\n${this.staffSummary.active} / ${this.staffSummary.total}`,
  865. fontSize:22,
  866. fontWeight:'bold',
  867. lineHeight:34
  868. },
  869. data:this.staffData
  870. }
  871. ]
  872. })
  873. this.staffLoading = false;
  874. },
  875. initOrderChart() {
  876. const chart = echarts.init(this.$refs.orderChart)
  877. chart.setOption({
  878. grid:{
  879. left:10,
  880. right:10,
  881. top:40,
  882. bottom:-5,
  883. containLabel:true
  884. },
  885. tooltip:{
  886. trigger:'axis'
  887. },
  888. legend:{
  889. top:0
  890. },
  891. xAxis:{
  892. type:'category',
  893. splitLine: { show: false },
  894. data:this.modes
  895. },
  896. yAxis:{
  897. splitLine: { show: false },
  898. type:'value'
  899. },
  900. series:[
  901. {
  902. name:'AI派单',
  903. type:'bar',
  904. barWidth:14,
  905. itemStyle:{
  906. normal: {
  907. barBorderRadius: [10, 10, 0, 0],
  908. color:new echarts.graphic.LinearGradient(
  909. 0,0,0,1,
  910. [
  911. {offset:0,color:'#5F5BFF'},
  912. {offset:1,color:'#B046FF'}
  913. ]
  914. )
  915. },
  916. },
  917. barCategoryGap: "50%",
  918. data:this.orderAi
  919. },
  920. {
  921. name:'人工派单',
  922. type:'line',
  923. smooth:true,
  924. symbol:'circle',
  925. symbolSize:8,
  926. itemStyle:{
  927. color:'#2A8CFF'
  928. },
  929. areaStyle:{
  930. color:new echarts.graphic.LinearGradient(
  931. 0,0,0,1,
  932. [
  933. {
  934. offset:0,
  935. color:'rgba(42,140,255,.25)'
  936. },
  937. {
  938. offset:1,
  939. color:'rgba(42,140,255,0)'
  940. }
  941. ]
  942. )
  943. },
  944. data:this.orderUser
  945. }
  946. ]
  947. })
  948. this.orderLoading = false;
  949. },
  950. observe(refName, callback) {
  951. const observer = new IntersectionObserver(entries => {
  952. if (entries[0].isIntersecting) {
  953. callback()
  954. observer.disconnect()
  955. }
  956. }, { threshold: 0.1 })
  957. observer.observe(this.$refs[refName])
  958. },
  959. updateCountByValue(value, newCount) {
  960. const item = this.tabs.find(tab => tab.value === value);
  961. if (item) {
  962. const oldCount = item.count; // 保存旧值
  963. item.count = newCount;
  964. this.$nextTick(() => {
  965. const el = this.$refs[`count_${value}`];
  966. if (el && el[0]) {
  967. this.animateNumber(el[0], oldCount, newCount, 1000);
  968. }
  969. });
  970. }
  971. },
  972. animateNumber(el, start, end, duration = 1000, callback = null) {
  973. if (start === end) {
  974. el.textContent = this.formatNumber(end);
  975. if (callback) callback();
  976. return;
  977. }
  978. const startTime = performance.now();
  979. const diff = end - start;
  980. const update = (currentTime) => {
  981. let progress = Math.min((currentTime - startTime) / duration, 1);
  982. const easeOut = 1 - Math.pow(1 - progress, 3);
  983. const current = Math.round(start + diff * easeOut);
  984. // 格式化显示
  985. el.textContent = this.formatNumber(current);
  986. if (progress < 1) {
  987. requestAnimationFrame(update);
  988. } else {
  989. el.textContent = this.formatNumber(end);
  990. if (callback) callback();
  991. }
  992. };
  993. requestAnimationFrame(update);
  994. },
  995. formatNumber(num) {
  996. return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
  997. },
  998. layerOpen(id){
  999. layer.open({
  1000. type: 2,
  1001. title: "详情",
  1002. shadeClose: true,
  1003. shade: 0.3,
  1004. maxmin: true,
  1005. area: ['80%', '80%'],
  1006. content: "{:url('orders/detail2',[],'')}/id/"+id,
  1007. end: function () {
  1008. // window.location.reload();
  1009. }
  1010. });
  1011. },
  1012. send(id,mode,type){
  1013. const self = this;
  1014. layer.open({
  1015. type: 2,
  1016. title: "派单",
  1017. shadeClose: true,
  1018. shade: 0.3,
  1019. maxmin: true,
  1020. area: ['80%', '80%'],
  1021. content: "{:url('send',[],'')}/id/"+id+"/mode/"+mode+"/type/"+type,
  1022. end: function () {
  1023. self.tableLoading = true;
  1024. if (type === 0){
  1025. self.fetchDispatchedData();
  1026. }else{
  1027. self.fetchUnDispatchedData();
  1028. }
  1029. }
  1030. });
  1031. },
  1032. },
  1033. });
  1034. </script>
  1035. <script>
  1036. /**
  1037. * 菜单配置
  1038. */
  1039. var menuConfig = {
  1040. dispatch: [
  1041. {
  1042. title:'待派单列表',
  1043. url:'UnDispatched.html'
  1044. },
  1045. {
  1046. title:'已派单列表',
  1047. url:'history.html'
  1048. },
  1049. {
  1050. title:'异常单列表',
  1051. url:"{:url('abnormal',['type'=>1])}"
  1052. },
  1053. {
  1054. title:'保洁单列表',
  1055. url:"{:url('orders/index',['mode'=>2])}"
  1056. },
  1057. {
  1058. title:'运送单列表',
  1059. url: "{:url('orders/index',['mode'=>3])}"
  1060. },
  1061. {
  1062. title:'维修单列表',
  1063. url:"{:url('orders/index',['mode'=>1])}"
  1064. },
  1065. {
  1066. title:'安保单列表',
  1067. url:"{:url('orders/index',['mode'=>4])}"
  1068. }
  1069. ],
  1070. setting: [
  1071. {
  1072. title:'基础设置',
  1073. url:'config.html'
  1074. },
  1075. {
  1076. title:'人员技能标签',
  1077. url:'userskill.html'
  1078. },
  1079. {
  1080. title:'维修部门负责人',
  1081. url:'dep.html'
  1082. },
  1083. {
  1084. title:'运送地点负责人',
  1085. url:'address.html'
  1086. }
  1087. ]
  1088. };
  1089. /**
  1090. * 渲染菜单
  1091. */
  1092. function renderSidebar(type){
  1093. showLoading('一大批数据正在赶来...');
  1094. var menus = menuConfig[type];
  1095. var html='';
  1096. $.each(menus,function(index,item){
  1097. html += `
  1098. <div
  1099. class="menu-item ${index===0?'active':''}"
  1100. data-url="${item.url}">
  1101. ${item.title}
  1102. </div>
  1103. `;
  1104. });
  1105. $('.sidebar').html(html);
  1106. if(menus.length){
  1107. $('#contentFrame').attr(
  1108. 'src',
  1109. menus[0].url
  1110. );
  1111. }
  1112. }
  1113. $('#contentFrame').on('load', function () {
  1114. hideLoading();
  1115. });
  1116. /**
  1117. * 顶部菜单切换
  1118. */
  1119. $('.top-item').click(function(){
  1120. $('.top-item').removeClass('active');
  1121. $(this).addClass('active');
  1122. var type = $(this).data('type');
  1123. if(type === 'ai'){
  1124. $('#mainContainer').hide();
  1125. $('#aiContainer').show();
  1126. return;
  1127. }
  1128. $('#aiContainer').hide();
  1129. $('#mainContainer').show();
  1130. renderSidebar(type);
  1131. });
  1132. /**
  1133. * 左侧菜单切换
  1134. */
  1135. $(document).on(
  1136. 'click',
  1137. '.menu-item',
  1138. function(){
  1139. $('.menu-item').removeClass('active');
  1140. $(this).addClass('active');
  1141. showLoading('一大批数据正在赶来...');
  1142. $('#contentFrame').attr(
  1143. 'src',
  1144. $(this).data('url')
  1145. );
  1146. }
  1147. );
  1148. var loadingStart = 0;
  1149. function showLoading(text){
  1150. loadingStart = Date.now();
  1151. $('#pageLoading').show();
  1152. if(text){
  1153. $('.loading-text').text(text);
  1154. }
  1155. }
  1156. function hideLoading(){
  1157. var spend = Date.now() - loadingStart;
  1158. var delay = Math.max(0,300-spend);
  1159. setTimeout(function(){
  1160. $('#pageLoading').fadeOut(200);
  1161. },delay);
  1162. }
  1163. </script>
  1164. </body>
  1165. </html>