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