workOrder.vue 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360
  1. <template>
  2. <view class="mainBox">
  3. <uni-nav-bar
  4. fixed
  5. statusBar
  6. left-icon="back"
  7. :title="navTitle"
  8. @clickLeft="back"
  9. />
  10. <!-- 搜索区域 -->
  11. <view class="top-wrapper">
  12. <input class="search-input" v-model="searchName" placeholder="演练名称" />
  13. <!-- <view class="dept-btn" @click="showDeptPicker">
  14. <text>{{ deptName || '主管部门' }}</text>
  15. <text class="arrow">▾</text>
  16. </view> -->
  17. <button class="search_btn" @click="handleSearch">搜索</button>
  18. </view>
  19. <!-- 列表 -->
  20. <view class="wrapper">
  21. <u-list @scrolltolower="scrolltolower" class="listContent">
  22. <view
  23. v-for="(item, idx) in listData"
  24. :key="item.id"
  25. style="position: relative"
  26. >
  27. <myCard
  28. :item="item"
  29. :index="idx + 1"
  30. :btnList="getBtnList(item)"
  31. :columns="cardColumns"
  32. :title="item.name"
  33. :status="getStatusLabel(item.status)"
  34. @goDetail="goDetail"
  35. @handleReport="handleReport(item)"
  36. @handleAcceptance="handleAcceptance(item)"
  37. />
  38. </view>
  39. <view style="width: 100%; height: 40rpx"></view>
  40. <view style="margin-top: 20vh" v-if="!loading && listData.length == 0">
  41. <u-empty iconSize="150" textSize="32" text="暂无数据" />
  42. </view>
  43. <view v-if="loading" class="load-more">加载中...</view>
  44. <view v-if="isEnd && listData.length > 0" class="load-more"
  45. >没有更多了</view
  46. >
  47. </u-list>
  48. </view>
  49. <!-- 新增按钮 -->
  50. <!-- <view class="add" @click="handleAdd">
  51. <u-icon name="plus" color="#fff"></u-icon>
  52. </view> -->
  53. <!-- 弹窗 -->
  54. <workOrderDialog ref="dialogRef" @reload="reloadList" />
  55. <workOrderReportDialog ref="reportRef" @reload="reloadList" />
  56. <workOrderReportAcceptanceDialog ref="acceptanceRef" @reload="reloadList" />
  57. <!-- 部门选择器 -->
  58. <ba-tree-picker
  59. ref="treePicker"
  60. key="dept"
  61. :multiple="false"
  62. @select-change="onDeptConfirm"
  63. title="选择部门"
  64. :localdata="deptList"
  65. valueKey="id"
  66. textKey="name"
  67. />
  68. <u-toast ref="uToast" />
  69. </view>
  70. </template>
  71. <script>
  72. import myCard from "@/components/myCard.vue";
  73. import workOrderDialog from "./workOrderDialog.vue";
  74. import workOrderReportDialog from "./workOrderReportDialog.vue";
  75. import workOrderReportAcceptanceDialog from "./workOrderReportAcceptanceDialog.vue";
  76. import { getList } from "@/api/emergencyDrill/workOrder.js";
  77. import dictMixns from "@/mixins/dictMixins";
  78. export default {
  79. mixins: [dictMixns],
  80. components: {
  81. myCard,
  82. workOrderDialog,
  83. workOrderReportDialog,
  84. workOrderReportAcceptanceDialog,
  85. },
  86. data() {
  87. return {
  88. searchName: "",
  89. deptId: null,
  90. deptName: "主管部门",
  91. deptList: [],
  92. listData: [],
  93. page: 1,
  94. size: 10,
  95. isEnd: false,
  96. loading: false,
  97. cardColumns: [
  98. [{ prop: "code", label: "编码", className: "perce100" }],
  99. [
  100. {
  101. prop: "drillType",
  102. label: "演练类型",
  103. className: "perce50",
  104. formatter: (row) => {
  105. return (
  106. this.getDictValue("应急演练类型", row.drillType)
  107. );
  108. },
  109. },
  110. ],
  111. [
  112. {
  113. prop: "superviseDeptName",
  114. label: "主管部门",
  115. className: "perce100",
  116. },
  117. ],
  118. [{ prop: "planLocation", label: "演练地点", className: "perce100" }],
  119. [
  120. {
  121. prop: "emergencyPlanName",
  122. label: "应急预案",
  123. className: "perce100",
  124. },
  125. ],
  126. [
  127. {
  128. label: "操作",
  129. prop: "action",
  130. type: "action",
  131. className: "perce100",
  132. },
  133. ],
  134. ],
  135. };
  136. },
  137. computed: {
  138. navTitle() {
  139. return "应急演练工单";
  140. },
  141. },
  142. onLoad() {
  143. this.getList();
  144. this.requestDict('应急演练类型')
  145. },
  146. methods: {
  147. // 部门选择
  148. showDeptPicker() {
  149. this.$refs.treePicker._show();
  150. },
  151. onDeptConfirm(data) {
  152. const id = data[0];
  153. this.deptId = id;
  154. // 查找部门名称
  155. const findDept = (list) => {
  156. for (const item of list) {
  157. if (item.id === id) return item.name;
  158. if (item.children && item.children.length > 0) {
  159. const found = findDept(item.children);
  160. if (found) return found;
  161. }
  162. }
  163. return null;
  164. };
  165. this.deptName = findDept(this.deptList) || "主管部门";
  166. this.page = 1;
  167. this.isEnd = false;
  168. this.getList();
  169. },
  170. // 状态标签
  171. getStatusLabel(status) {
  172. const map = {
  173. 0: "待演练",
  174. 1: "待验收",
  175. 2: "已完成",
  176. };
  177. return map[status] || status;
  178. },
  179. // 按钮列表
  180. getBtnList(item) {
  181. const btns = [];
  182. // 报工(待演练状态)
  183. if (item.status == 0) {
  184. btns.push({
  185. name: "报工",
  186. apiName: "handleReport",
  187. btnType: "primary",
  188. judge: [{ fn: () => true }],
  189. });
  190. }
  191. // 验收(待验收状态)
  192. if (item.status == 1) {
  193. btns.push({
  194. name: "验收",
  195. apiName: "handleAcceptance",
  196. btnType: "success",
  197. judge: [{ fn: () => true }],
  198. });
  199. }
  200. return btns;
  201. },
  202. // 列表操作
  203. goDetail(item) {
  204. this.$refs.dialogRef.open(item, "view");
  205. },
  206. handleAdd() {
  207. // 需要先选择演练计划,简化处理:打开弹窗后选择
  208. this.$refs.dialogRef.open(null, "add");
  209. },
  210. handleReport(item) {
  211. this.$refs.reportRef.open(item);
  212. },
  213. handleAcceptance(item) {
  214. this.$refs.acceptanceRef.open(item);
  215. },
  216. // 搜索
  217. handleSearch() {
  218. this.page = 1;
  219. this.isEnd = false;
  220. this.getList();
  221. },
  222. // 刷新列表
  223. reloadList() {
  224. this.page = 1;
  225. this.isEnd = false;
  226. this.getList();
  227. },
  228. // 加载数据
  229. async getList() {
  230. if (this.loading || this.isEnd) return;
  231. this.loading = true;
  232. try {
  233. const params = {
  234. pageNum: this.page,
  235. size: this.size,
  236. name: this.searchName || undefined,
  237. };
  238. const res = await getList(params);
  239. const list = res.list || [];
  240. if (this.page === 1) {
  241. this.listData = list;
  242. } else {
  243. this.listData = this.listData.concat(list);
  244. }
  245. this.isEnd =
  246. list.length < this.size || this.listData.length >= res.count;
  247. this.page += 1;
  248. } catch (e) {
  249. console.error("加载失败", e);
  250. this.$refs.uToast.show({ type: "error", message: "加载失败,请重试" });
  251. } finally {
  252. this.loading = false;
  253. }
  254. },
  255. scrolltolower() {
  256. if (!this.isEnd && !this.loading) {
  257. this.getList();
  258. }
  259. },
  260. },
  261. };
  262. </script>
  263. <style lang="scss" scoped>
  264. .mainBox {
  265. background-color: #f3f8fb;
  266. min-height: 100vh;
  267. }
  268. .top-wrapper {
  269. background: #fff;
  270. padding: 16rpx 24rpx;
  271. display: flex;
  272. align-items: center;
  273. border-bottom: 2rpx solid #eee;
  274. gap: 12rpx;
  275. .search-input {
  276. flex: 1;
  277. height: 64rpx;
  278. border: 2rpx solid #e9edf2;
  279. border-radius: 48rpx;
  280. padding: 0 24rpx;
  281. font-size: 26rpx;
  282. background: #fafafa;
  283. min-width: 0;
  284. }
  285. .dept-btn {
  286. display: flex;
  287. align-items: center;
  288. height: 64rpx;
  289. padding: 0 20rpx;
  290. background: #e8edf4;
  291. border-radius: 48rpx;
  292. color: #2979ff;
  293. font-size: 24rpx;
  294. flex-shrink: 0;
  295. gap: 4rpx;
  296. .arrow {
  297. font-size: 18rpx;
  298. }
  299. }
  300. .search_btn {
  301. height: 64rpx;
  302. padding: 0 28rpx;
  303. background: $theme-color;
  304. font-size: 26rpx;
  305. color: #fff;
  306. border-radius: 48rpx;
  307. text-align: center;
  308. line-height: 64rpx;
  309. flex-shrink: 0;
  310. }
  311. }
  312. .wrapper {
  313. padding: 16rpx 24rpx;
  314. }
  315. .add {
  316. position: fixed;
  317. bottom: 100rpx;
  318. right: 40rpx;
  319. width: 100rpx;
  320. height: 100rpx;
  321. border-radius: 50%;
  322. background: $theme-color;
  323. display: flex;
  324. align-items: center;
  325. justify-content: center;
  326. box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.15);
  327. z-index: 999;
  328. }
  329. .load-more {
  330. text-align: center;
  331. font-size: 26rpx;
  332. color: #aaa;
  333. padding: 20rpx 0 40rpx;
  334. }
  335. </style>