gongdan.html 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  5. <meta http-equiv="X-UA-Compatible" content="IE=edge">
  6. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  7. <script src="./file/axios.min.js"></script>
  8. <script src="./file/vue.min.js"></script>
  9. <link rel="stylesheet" href="./file/index.css">
  10. <script src="./file/vant.min.js"></script>
  11. <link rel="stylesheet" href="./file/style.css">
  12. <script src="./file/login-auth.js"></script>
  13. <title>工单</title>
  14. <style>
  15. body {
  16. background-color: #f5f6fa;
  17. margin: 0;
  18. font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'PingFang SC', sans-serif;
  19. }
  20. .container {
  21. max-width: 750px;
  22. margin: 0 auto;
  23. background-color: #f5f6fa;
  24. min-height: 100vh;
  25. }
  26. .nav-bar {
  27. display: flex;
  28. justify-content: space-between;
  29. align-items: center;
  30. height: 44px;
  31. padding: 0 16px;
  32. background-color: #fff;
  33. border-bottom: 1px solid #eee;
  34. position: sticky;
  35. top: 0;
  36. z-index: 100;
  37. }
  38. .nav-bar .nav-left, .nav-bar .nav-right {
  39. width: 44px;
  40. height: 44px;
  41. display: flex;
  42. align-items: center;
  43. justify-content: center;
  44. cursor: pointer;
  45. }
  46. .nav-bar .nav-left svg, .nav-bar .nav-right svg {
  47. width: 20px;
  48. height: 20px;
  49. fill: #333;
  50. }
  51. .nav-bar .nav-right { gap: 16px; }
  52. .nav-bar .nav-title {
  53. font-size: 17px;
  54. font-weight: 600;
  55. color: #000;
  56. }
  57. /* 搜索栏 */
  58. .search-bar {
  59. display: flex;
  60. align-items: center;
  61. padding: 8px 12px;
  62. background-color: #fff;
  63. border-bottom: 1px solid #eee;
  64. }
  65. .search-input-wrap {
  66. flex: 1;
  67. display: flex;
  68. align-items: center;
  69. background-color: #f5f5f5;
  70. border-radius: 4px;
  71. padding: 6px 10px;
  72. }
  73. .search-input-wrap svg {
  74. width: 16px;
  75. height: 16px;
  76. fill: #999;
  77. margin-right: 6px;
  78. }
  79. .search-input-wrap input {
  80. flex: 1;
  81. border: none;
  82. background: transparent;
  83. outline: none;
  84. font-size: 14px;
  85. color: #333;
  86. }
  87. .search-cancel {
  88. margin-left: 12px;
  89. font-size: 14px;
  90. color: #1989fa;
  91. cursor: pointer;
  92. }
  93. /* 筛选弹窗 */
  94. .filter-mask {
  95. position: fixed;
  96. top: 0; left: 0; right: 0; bottom: 0;
  97. background: rgba(0,0,0,0.5);
  98. z-index: 999;
  99. }
  100. .filter-panel {
  101. position: fixed;
  102. top: 0;
  103. right: 0;
  104. bottom: 0;
  105. width: 85%;
  106. max-width: 350px;
  107. background: #fff;
  108. box-shadow: -2px 0 8px rgba(0,0,0,0.1);
  109. display: flex;
  110. flex-direction: column;
  111. z-index: 1000;
  112. transform: translateX(100%);
  113. transition: transform 0.3s ease;
  114. }
  115. .filter-panel.show { transform: translateX(0); }
  116. .filter-header {
  117. padding: 16px;
  118. display: flex;
  119. justify-content: space-between;
  120. align-items: center;
  121. border-bottom: 1px solid #eee;
  122. }
  123. .filter-header span { font-size: 17px; font-weight: 600; }
  124. .filter-close { font-size: 24px; color: #999; cursor: pointer; }
  125. .filter-body {
  126. flex: 1;
  127. overflow-y: auto;
  128. padding: 16px;
  129. }
  130. .filter-section { margin-bottom: 24px; }
  131. .filter-section-title {
  132. font-size: 14px;
  133. font-weight: 500;
  134. color: #333;
  135. margin-bottom: 12px;
  136. }
  137. .filter-tags { display: flex; flex-wrap: wrap; gap: 10px; }
  138. .tag-item {
  139. padding: 8px 16px;
  140. background: #f5f5f5;
  141. border-radius: 4px;
  142. font-size: 14px;
  143. color: #333;
  144. cursor: pointer;
  145. }
  146. .tag-item.active { background: #1989fa; color: #fff; }
  147. .filter-date-row {
  148. display: flex;
  149. align-items: center;
  150. gap: 10px;
  151. }
  152. .filter-date-row input {
  153. flex: 1;
  154. padding: 10px;
  155. border: 1px solid #ddd;
  156. border-radius: 4px;
  157. font-size: 14px;
  158. color: #333;
  159. background: #fff;
  160. }
  161. .filter-date-row span { color: #999; }
  162. .filter-footer {
  163. display: flex;
  164. padding: 16px;
  165. gap: 12px;
  166. border-top: 1px solid #eee;
  167. }
  168. .btn-reset {
  169. flex: 1;
  170. text-align: center;
  171. padding: 12px;
  172. border: 1px solid #ddd;
  173. border-radius: 4px;
  174. color: #1989fa;
  175. font-size: 15px;
  176. cursor: pointer;
  177. }
  178. .btn-confirm {
  179. flex: 1;
  180. text-align: center;
  181. padding: 12px;
  182. background: #1989fa;
  183. border-radius: 4px;
  184. color: #fff;
  185. font-size: 15px;
  186. cursor: pointer;
  187. }
  188. /* 标签页 */
  189. .tabNav {
  190. display: flex;
  191. background-color: #fff;
  192. border-bottom: 1px solid #eee;
  193. }
  194. .tabNav > a {
  195. display: block;
  196. flex: 1;
  197. text-align: center;
  198. line-height: 44px;
  199. font-size: 15px;
  200. color: #666;
  201. text-decoration: none;
  202. position: relative;
  203. }
  204. .tabNav > a.active {
  205. color: #1989fa;
  206. font-weight: 500;
  207. }
  208. .tabNav > a.active::after {
  209. content: '';
  210. position: absolute;
  211. bottom: 0;
  212. left: 50%;
  213. transform: translateX(-50%);
  214. width: 40px;
  215. height: 3px;
  216. background-color: #1989fa;
  217. border-radius: 2px;
  218. }
  219. /* 列表样式 */
  220. .list-container { padding: 12px; background-color: #f5f6fa; }
  221. .card {
  222. background-color: #fff;
  223. border-radius: 8px;
  224. padding: 16px;
  225. margin-bottom: 12px;
  226. box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
  227. }
  228. .card-header {
  229. display: flex;
  230. justify-content: space-between;
  231. align-items: center;
  232. padding-bottom: 12px;
  233. border-bottom: 1px solid #f0f0f0;
  234. }
  235. .tag {
  236. display: inline-block;
  237. background-color: #1989fa;
  238. color: #fff;
  239. font-size: 12px;
  240. padding: 4px 8px;
  241. border-radius: 4px;
  242. }
  243. .status { font-size: 14px; font-weight: 500; }
  244. .status.pending { color: #1989fa; }
  245. .status.processing { color: #07c160; }
  246. .status.completed { color: #ff976a; }
  247. .status.paused { color: #ee0a24; }
  248. .card-body { padding-top: 12px; }
  249. .address {
  250. font-size: 15px;
  251. font-weight: 500;
  252. color: #333;
  253. line-height: 1.5;
  254. margin-bottom: 8px;
  255. }
  256. .info-row {
  257. display: flex;
  258. font-size: 13px;
  259. color: #999;
  260. margin-bottom: 6px;
  261. line-height: 1.4;
  262. }
  263. .label { flex-shrink: 0; width: 70px; }
  264. .value { flex: 1; word-break: break-all; }
  265. .value.phone { color: #1989fa; text-decoration: none; }
  266. .empty-state {
  267. text-align: center;
  268. padding: 40px 0;
  269. color: #999;
  270. font-size: 14px;
  271. }
  272. </style>
  273. </head>
  274. <body>
  275. <div class="container">
  276. <div id="app">
  277. <!-- 顶部导航栏 -->
  278. <div class="nav-bar">
  279. <div class="nav-left" @click="goback">
  280. <svg viewBox="0 0 24 24"><path d="M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z"/></svg>
  281. </div>
  282. <div class="nav-title">工单</div>
  283. <div class="nav-right">
  284. <svg @click="showSearch = true" viewBox="0 0 24 24"><path d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"/></svg>
  285. <svg @click="showFilter = true" viewBox="0 0 24 24"><path d="M3 17v2h6v-2H3zM3 5v2h10V5H3zm10 16v-2h8v-2h-8v-2h-2v6h2zM7 9v2H3v2h4v2h2V9H7zm14 4v-2H11v2h10zm-6-4h2V7h4V5h-4V3h-2v6z"/></svg>
  286. </div>
  287. </div>
  288. <!-- 搜索栏 -->
  289. <div class="search-bar" v-show="showSearch">
  290. <div class="search-input-wrap">
  291. <svg viewBox="0 0 24 24"><path d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"/></svg>
  292. <input type="text" v-model="searchKey" placeholder="请输入关键词搜索" />
  293. </div>
  294. <div class="search-cancel" @click="showSearch = false; searchKey = ''">取消</div>
  295. </div>
  296. <!-- 标签页导航 -->
  297. <div class="tabNav">
  298. <a href="jindu.html">预约单</a>
  299. <a class="active">工单</a>
  300. </div>
  301. <div class="list-container">
  302. <div v-if="filteredList.length === 0" class="empty-state">暂无工单数据</div>
  303. <div class="card" v-for="(item, index) in filteredList" :key="index" @click="goDetail(item.sc_workorderid)">
  304. <div class="card-header">
  305. <span class="tag">{{ item.type || '施工' }}</span>
  306. <span class="status" :class="{
  307. 'pending': item.status === '待开始',
  308. 'processing': item.status === '进行中',
  309. 'paused': item.status === '暂停',
  310. 'completed': item.status === '已完成'
  311. }">{{ item.status }}</span>
  312. </div>
  313. <div class="card-body">
  314. <div class="address">{{ item.address }}</div>
  315. <div class="info-row">
  316. <span class="label">工单号:</span>
  317. <span class="value">{{ item.billno }}</span>
  318. </div>
  319. <div class="info-row">
  320. <span class="label">客户:</span>
  321. <span class="value">{{ item.name }} <a :href="'tel:' + item.phonenumber" class="value phone">{{ item.phonenumber }}</a></span>
  322. </div>
  323. <div class="info-row">
  324. <span class="label">现场联系人:</span>
  325. <span class="value">{{ item.scenecontact }} <a :href="'tel:' + item.scenecontactphonenumber" class="value phone">{{ item.scenecontactphonenumber }}</a></span>
  326. </div>
  327. <div class="info-row">
  328. <span class="label">负责人:</span>
  329. <span class="value">{{ item.changeby || '-' }}</span>
  330. </div>
  331. </div>
  332. </div>
  333. </div>
  334. <!-- 筛选弹窗 -->
  335. <div class="filter-mask" v-show="showFilter" @click="showFilter = false">
  336. <div class="filter-panel" :class="{ show: showFilter }" @click.stop>
  337. <div class="filter-header">
  338. <span>筛选条件</span>
  339. <div class="filter-close" @click="showFilter = false">×</div>
  340. </div>
  341. <div class="filter-body">
  342. <div class="filter-section">
  343. <div class="filter-section-title">状态</div>
  344. <div class="filter-tags">
  345. <div class="tag-item" :class="{ active: filters.status === '待开始' }" @click="filters.status = '待开始'">待开始</div>
  346. <div class="tag-item" :class="{ active: filters.status === '进行中' }" @click="filters.status = '进行中'">进行中</div>
  347. <div class="tag-item" :class="{ active: filters.status === '暂停' }" @click="filters.status = '暂停'">暂停</div>
  348. <div class="tag-item" :class="{ active: filters.status === '已完成' }" @click="filters.status = '已完成'">已完成</div>
  349. <div class="tag-item" :class="{ active: filters.status === '质保卡审批' }" @click="filters.status = '质保卡审批'">质保卡审批</div>
  350. <div class="tag-item" :class="{ active: filters.status === '作废' }" @click="filters.status = '作废'">作废</div>
  351. </div>
  352. </div>
  353. <div class="filter-section">
  354. <div class="filter-section-title">工单类型</div>
  355. <div class="filter-tags">
  356. <div class="tag-item" :class="{ active: filters.orderType === '施工' }" @click="filters.orderType = '施工'">施工</div>
  357. <div class="tag-item" :class="{ active: filters.orderType === '维修' }" @click="filters.orderType = '维修'">维修</div>
  358. <div class="tag-item" :class="{ active: filters.orderType === '验收' }" @click="filters.orderType = '验收'">验收</div>
  359. </div>
  360. </div>
  361. <div class="filter-section">
  362. <div class="filter-section-title">服务类型</div>
  363. <div class="filter-tags">
  364. <div class="tag-item" :class="{ active: filters.type === '安装' }" @click="filters.type = '安装'">安装</div>
  365. <div class="tag-item" :class="{ active: filters.type === '维修' }" @click="filters.type = '维修'">维修</div>
  366. <div class="tag-item" :class="{ active: filters.type === '验收' }" @click="filters.type = '验收'">验收</div>
  367. </div>
  368. </div>
  369. <div class="filter-section">
  370. <div class="filter-section-title">创建时间</div>
  371. <div class="filter-date-row">
  372. <input type="date" v-model="filters.createDateStart" placeholder="开始日期" />
  373. <span>—</span>
  374. <input type="date" v-model="filters.createDateEnd" placeholder="结束日期" />
  375. </div>
  376. </div>
  377. <div class="filter-section">
  378. <div class="filter-section-title">完成时间</div>
  379. <div class="filter-date-row">
  380. <input type="date" v-model="filters.finishDateStart" placeholder="开始日期" />
  381. <span>—</span>
  382. <input type="date" v-model="filters.finishDateEnd" placeholder="结束日期" />
  383. </div>
  384. </div>
  385. </div>
  386. <div class="filter-footer">
  387. <div class="btn-reset" @click="resetFilter">重置</div>
  388. <div class="btn-confirm" @click="applyFilter">确定</div>
  389. </div>
  390. </div>
  391. </div>
  392. </div>
  393. </div>
  394. <script>
  395. new Vue({
  396. el: '#app',
  397. data: {
  398. workOrderData: [],
  399. showSearch: false,
  400. searchKey: '',
  401. showFilter: false,
  402. filters: {
  403. status: '',
  404. orderType: '',
  405. type: '',
  406. createDateStart: '',
  407. createDateEnd: '',
  408. finishDateStart: '',
  409. finishDateEnd: ''
  410. }
  411. },
  412. computed: {
  413. filteredList () {
  414. return this.workOrderData;
  415. }
  416. },
  417. watch: {
  418. showFilter (val) {
  419. if (val) {
  420. document.body.classList.add('van-overflow-hidden');
  421. } else {
  422. document.body.classList.remove('van-overflow-hidden');
  423. }
  424. }
  425. },
  426. methods: {
  427. goback () {
  428. window.location.href="javascript:history.go(-1)";
  429. },
  430. goDetail (id) {
  431. window.location.href = 'gongdan_detail.html?id=' + id;
  432. },
  433. resetFilter () {
  434. this.filters = { status: '', orderType: '', type: '', createDateStart: '', createDateEnd: '', finishDateStart: '', finishDateEnd: '' };
  435. this.getData();
  436. },
  437. applyFilter () {
  438. this.showFilter = false;
  439. this.getData();
  440. },
  441. getData() {
  442. var self = this;
  443. var login = window.H5Login || {};
  444. axios.post(window.API_URL, {
  445. id: 2026052113525502,
  446. content: {
  447. phonenumber: login.phone || '',
  448. pageNumber: 1,
  449. pageSize: 50,
  450. where: {
  451. condition: this.searchKey || '',
  452. status: this.filters.status ? [this.filters.status] : [],
  453. type: this.filters.orderType || '',
  454. servicetype: this.filters.type || '',
  455. begindate_createdate: this.filters.createDateStart || '',
  456. enddate_createdate: this.filters.createDateEnd || '',
  457. begindate_finishdate: this.filters.finishDateStart || '',
  458. enddate_finishdate: this.filters.finishDateEnd || ''
  459. }
  460. },
  461. accesstoken: login.token || ''
  462. }).then(function (res) {
  463. var data = res.data;
  464. if (data.code === 1) {
  465. self.workOrderData = data.data || [];
  466. }
  467. }).catch(function () {
  468. self.workOrderData = [];
  469. });
  470. }
  471. },
  472. mounted() {
  473. var self = this;
  474. this.getData();
  475. window.addEventListener('h5:login', function () {
  476. self.getData();
  477. });
  478. }
  479. })
  480. </script>
  481. </body>
  482. </html>