index2.vue 31 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168
  1. <template>
  2. <div class="repair-order-container">
  3. <!-- 页面标题 -->
  4. <div class="page-header">
  5. <h2 class="page-title">
  6. <el-icon><Tools /></el-icon>
  7. 维修工单管理
  8. </h2>
  9. <div class="header-actions">
  10. <!-- <el-button type="primary" :icon="Plus" @click="handleAdd" v-hasPermi="['repairOrder:repairOrder:add']">
  11. 新建工单
  12. </el-button>-->
  13. <el-button :icon="Download" @click="handleExport" v-hasPermi="['repairOrder:repairOrder:export']">
  14. 导出报表
  15. </el-button>
  16. </div>
  17. </div>
  18. <!-- 统计卡片 -->
  19. <el-row :gutter="20" class="statistics-cards">
  20. <el-col :xs="24" :sm="12" :md="6">
  21. <div class="stat-card">
  22. <div class="stat-icon blue">
  23. <el-icon><DocumentCopy /></el-icon>
  24. </div>
  25. <div class="stat-content">
  26. <div class="stat-value">{{ statistics.total }}</div>
  27. <div class="stat-label">工单总数</div>
  28. </div>
  29. </div>
  30. </el-col>
  31. <el-col :xs="24" :sm="12" :md="6">
  32. <div class="stat-card">
  33. <div class="stat-icon orange">
  34. <el-icon><Clock /></el-icon>
  35. </div>
  36. <div class="stat-content">
  37. <div class="stat-value">{{ statistics.pending }}</div>
  38. <div class="stat-label">待处理</div>
  39. </div>
  40. </div>
  41. </el-col>
  42. <el-col :xs="24" :sm="12" :md="6">
  43. <div class="stat-card">
  44. <div class="stat-icon purple">
  45. <el-icon><Loading /></el-icon>
  46. </div>
  47. <div class="stat-content">
  48. <div class="stat-value">{{ statistics.processing }}</div>
  49. <div class="stat-label">处理中</div>
  50. </div>
  51. </div>
  52. </el-col>
  53. <el-col :xs="24" :sm="12" :md="6">
  54. <div class="stat-card">
  55. <div class="stat-icon green">
  56. <el-icon><CircleCheck /></el-icon>
  57. </div>
  58. <div class="stat-content">
  59. <div class="stat-value">{{ statistics.completed }}</div>
  60. <div class="stat-label">已完成</div>
  61. </div>
  62. </div>
  63. </el-col>
  64. </el-row>
  65. <!-- 搜索栏 -->
  66. <el-card class="search-card" shadow="never">
  67. <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch">
  68. <el-row :gutter="20">
  69. <el-col :xs="24" :sm="12" :md="6">
  70. <el-form-item label="工单编号" prop="orderNo">
  71. <el-input
  72. v-model="queryParams.orderNo"
  73. placeholder="请输入工单编号"
  74. clearable
  75. :prefix-icon="Search"
  76. @keyup.enter="handleQuery"
  77. style="width: 100%"
  78. />
  79. </el-form-item>
  80. </el-col>
  81. <el-col :xs="24" :sm="12" :md="6">
  82. <el-form-item label="项目名称" prop="projectName">
  83. <el-input
  84. v-model="queryParams.projectName"
  85. placeholder="请输入项目名称"
  86. clearable
  87. :prefix-icon="Search"
  88. @keyup.enter="handleQuery"
  89. style="width: 100%"
  90. />
  91. </el-form-item>
  92. </el-col>
  93. <el-col :xs="24" :sm="12" :md="6">
  94. <el-form-item label="工单状态" prop="orderStatus">
  95. <el-select
  96. v-model="queryParams.orderStatus"
  97. placeholder="请选择工单状态"
  98. clearable
  99. style="width: 100%;min-width: 200px;"
  100. >
  101. <el-option
  102. v-for="dict in repair_status"
  103. :key="dict.value"
  104. :label="dict.label"
  105. :value="dict.value"
  106. >
  107. <div style="display: flex; align-items: center; justify-content: space-between; width: 100%;">
  108. <span>{{ dict.label }}</span>
  109. <el-tag :type="getStatusTagType(dict.value)" size="small">
  110. {{ dict.label }}
  111. </el-tag>
  112. </div>
  113. </el-option>
  114. </el-select>
  115. </el-form-item>
  116. </el-col>
  117. <el-col :xs="24" :sm="12" :md="6">
  118. <el-form-item label="派单时间" prop="dateRange">
  119. <el-date-picker
  120. v-model="queryParams.dateRange"
  121. type="daterange"
  122. range-separator="至"
  123. start-placeholder="开始日期"
  124. end-placeholder="结束日期"
  125. format="YYYY-MM-DD"
  126. value-format="YYYY-MM-DD"
  127. style="width: 100%"
  128. />
  129. </el-form-item>
  130. </el-col>
  131. </el-row>
  132. <el-row>
  133. <el-col :span="24" style="text-align: right;">
  134. <el-form-item>
  135. <el-button type="primary" :icon="Search" @click="handleQuery">搜索</el-button>
  136. <el-button :icon="Refresh" @click="resetQuery">重置</el-button>
  137. </el-form-item>
  138. </el-col>
  139. </el-row>
  140. </el-form>
  141. </el-card>
  142. <!-- 数据表格 -->
  143. <el-card class="table-card" shadow="never">
  144. <div class="table-header">
  145. <span class="table-title">工单列表</span>
  146. <div class="table-tools">
  147. <el-tooltip content="刷新" placement="top">
  148. <el-button :icon="Refresh" circle size="small" @click="getList" />
  149. </el-tooltip>
  150. <el-tooltip content="显示/隐藏搜索" placement="top">
  151. <el-button
  152. :icon="showSearch ? View : Hide"
  153. circle
  154. size="small"
  155. @click="showSearch = !showSearch"
  156. />
  157. </el-tooltip>
  158. </div>
  159. </div>
  160. <el-table
  161. v-loading="loading"
  162. :data="repairOrderList"
  163. @selection-change="handleSelectionChange"
  164. stripe
  165. border
  166. :row-class-name="tableRowClassName"
  167. >
  168. <el-table-column type="selection" width="50" align="center" />
  169. <el-table-column label="序号" type="index" width="60" align="center">
  170. <template #default="scope">
  171. {{ (queryParams.pageNum - 1) * queryParams.pageSize + scope.$index + 1 }}
  172. </template>
  173. </el-table-column>
  174. <el-table-column label="工单编号" align="center" prop="orderNo" min-width="120">
  175. <template #default="scope">
  176. <el-link type="primary" @click="showDetail(scope.row)">
  177. {{ scope.row.orderNo }}
  178. </el-link>
  179. </template>
  180. </el-table-column>
  181. <el-table-column label="工单内容" align="center" prop="orderContent" min-width="200" show-overflow-tooltip />
  182. <el-table-column label="项目名称" align="center" prop="projectName" min-width="150" show-overflow-tooltip />
  183. <el-table-column label="派单时间" align="center" prop="assignTime" width="180" sortable>
  184. <template #default="scope">
  185. <div class="time-info">
  186. <el-icon><Clock /></el-icon>
  187. <span>{{ parseTime(scope.row.assignTime, '{y}-{m}-{d} {h}:{i}') }}</span>
  188. </div>
  189. </template>
  190. </el-table-column>
  191. <el-table-column label="责任人" align="center" prop="finishBy" min-width="100">
  192. <template #default="scope">
  193. <div class="user-info">
  194. <el-avatar size="small" :src="getUserAvatar(scope.row.finishBy)">
  195. {{ scope.row.finishBy?.charAt(0) || '无' }}
  196. </el-avatar>
  197. <span>{{ scope.row.finishBy || '未分配' }}</span>
  198. </div>
  199. </template>
  200. </el-table-column>
  201. <el-table-column label="工单状态" align="center" prop="orderStatus" width="100">
  202. <template #default="scope">
  203. <el-tag
  204. :type="getStatusTagType(scope.row.orderStatus)"
  205. effect="dark"
  206. size="small"
  207. >
  208. <el-icon v-if="scope.row.orderStatus === '0'"><Clock /></el-icon>
  209. <el-icon v-else-if="scope.row.orderStatus === '1'"><Loading /></el-icon>
  210. <el-icon v-else-if="scope.row.orderStatus === '2'"><CircleCheck /></el-icon>
  211. {{ getStatusLabel(scope.row.orderStatus) }}
  212. </el-tag>
  213. </template>
  214. </el-table-column>
  215. <el-table-column label="完成时间" align="center" prop="finishTime" width="180">
  216. <template #default="scope">
  217. <span v-if="scope.row.finishTime">
  218. {{ parseTime(scope.row.finishTime, '{y}-{m}-{d} {h}:{i}') }}
  219. </span>
  220. <span v-else class="text-muted">-</span>
  221. </template>
  222. </el-table-column>
  223. <el-table-column label="附件" align="center" prop="annex" width="80">
  224. <template #default="scope">
  225. <el-button
  226. v-if="scope.row.annex"
  227. type="primary"
  228. link
  229. :icon="Paperclip"
  230. @click="downloadFile(scope.row.annex)"
  231. >
  232. 查看
  233. </el-button>
  234. <span v-else class="text-muted">-</span>
  235. </template>
  236. </el-table-column>
  237. <el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="200" fixed="right">
  238. <template #default="scope">
  239. <el-button
  240. link
  241. type="primary"
  242. :icon="View"
  243. @click="showDetail(scope.row)"
  244. v-hasPermi="['repairOrder:repairOrder:edit']"
  245. >
  246. 详情
  247. </el-button>
  248. <el-button
  249. v-if="scope.row.orderStatus === '0' || scope.row.orderStatus === '1'"
  250. link
  251. type="warning"
  252. :icon="Edit"
  253. @click="handleUpdate(scope.row)"
  254. v-hasPermi="['repairOrder:repairOrder:edit']"
  255. >
  256. 处理
  257. </el-button>
  258. <el-button
  259. link
  260. type="danger"
  261. :icon="Delete"
  262. @click="handleDelete(scope.row)"
  263. v-hasPermi="['repairOrder:repairOrder:remove']"
  264. >
  265. 删除
  266. </el-button>
  267. </template>
  268. </el-table-column>
  269. </el-table>
  270. <pagination
  271. v-show="total > 0"
  272. :total="total"
  273. v-model:page="queryParams.pageNum"
  274. v-model:limit="queryParams.pageSize"
  275. @pagination="getList"
  276. />
  277. </el-card>
  278. <!-- 工单详情抽屉 -->
  279. <el-drawer
  280. v-model="detailVisible"
  281. title="工单详情"
  282. direction="rtl"
  283. size="50%"
  284. class="detail-drawer"
  285. >
  286. <template #header>
  287. <div class="drawer-header">
  288. <h3>工单详情</h3>
  289. <el-tag :type="getStatusTagType(detailData.orderStatus)" size="large">
  290. {{ getStatusLabel(detailData.orderStatus) }}
  291. </el-tag>
  292. </div>
  293. </template>
  294. <div class="detail-content">
  295. <el-descriptions :column="2" border>
  296. <el-descriptions-item label="工单编号" :span="1">
  297. <span class="detail-value">{{ detailData.orderNo }}</span>
  298. </el-descriptions-item>
  299. <el-descriptions-item label="项目名称" :span="1">
  300. {{ detailData.projectName }}
  301. </el-descriptions-item>
  302. <el-descriptions-item label="工单内容" :span="2">
  303. {{ detailData.orderContent }}
  304. </el-descriptions-item>
  305. <el-descriptions-item label="派单时间">
  306. {{ parseTime(detailData.assignTime, '{y}-{m}-{d} {h}:{i}:{s}') }}
  307. </el-descriptions-item>
  308. <el-descriptions-item label="完成时间">
  309. {{ detailData.finishTime ? parseTime(detailData.finishTime, '{y}-{m}-{d} {h}:{i}:{s}') : '未完成' }}
  310. </el-descriptions-item>
  311. <el-descriptions-item label="责任人">
  312. <div class="user-info">
  313. <el-avatar size="small" :src="getUserAvatar(detailData.finishBy)">
  314. {{ detailData.finishBy?.charAt(0) || '无' }}
  315. </el-avatar>
  316. <span>{{ detailData.finishBy || '未分配' }}</span>
  317. </div>
  318. </el-descriptions-item>
  319. <el-descriptions-item label="工单状态">
  320. <el-tag :type="getStatusTagType(detailData.orderStatus)">
  321. {{ getStatusLabel(detailData.orderStatus) }}
  322. </el-tag>
  323. </el-descriptions-item>
  324. <el-descriptions-item label="工单备注" :span="2">
  325. {{ detailData.orderRemark || '无' }}
  326. </el-descriptions-item>
  327. </el-descriptions>
  328. <div v-if="detailData.annex" class="attachment-section">
  329. <h4>附件信息</h4>
  330. <el-button type="primary" :icon="Download" @click="downloadFile(detailData.annex)">
  331. 下载附件
  332. </el-button>
  333. </div>
  334. </div>
  335. </el-drawer>
  336. <!-- 添加或修改维修工单对话框 -->
  337. <el-dialog
  338. :title="title"
  339. v-model="open"
  340. width="700px"
  341. append-to-body
  342. :close-on-click-modal="false"
  343. >
  344. <el-form ref="repairOrderRef" :model="form" :rules="rules" label-width="100px">
  345. <el-row :gutter="20">
  346. <el-col :span="12">
  347. <el-form-item label="工单编号" prop="orderNo">
  348. <el-input v-model="form.orderNo" placeholder="请输入工单编号" disabled />
  349. </el-form-item>
  350. </el-col>
  351. <el-col :span="12">
  352. <el-form-item label="项目名称" prop="projectName">
  353. <el-input v-model="form.projectName" placeholder="请输入项目名称" disabled />
  354. </el-form-item>
  355. </el-col>
  356. </el-row>
  357. <el-form-item label="工单内容" prop="orderContent">
  358. <el-input
  359. v-model="form.orderContent"
  360. type="textarea"
  361. :rows="3"
  362. placeholder="请输入内容"
  363. disabled
  364. />
  365. </el-form-item>
  366. <el-row :gutter="20">
  367. <el-col :span="12">
  368. <el-form-item label="派单时间" prop="assignTime">
  369. <el-date-picker
  370. v-model="form.assignTime"
  371. type="datetime"
  372. value-format="YYYY-MM-DD HH:mm:ss"
  373. placeholder="请选择派单时间"
  374. disabled
  375. style="width: 100%"
  376. />
  377. </el-form-item>
  378. </el-col>
  379. <el-col :span="12">
  380. <el-form-item label="完成时间" prop="finishTime" :rules="rules.finishTime">
  381. <el-date-picker
  382. v-model="form.finishTime"
  383. type="datetime"
  384. value-format="YYYY-MM-DD HH:mm:ss"
  385. placeholder="请选择完成时间"
  386. style="width: 100%"
  387. :disabled-date="disabledDate"
  388. />
  389. </el-form-item>
  390. </el-col>
  391. </el-row>
  392. <el-form-item label="责任人" prop="finishBy">
  393. <el-select
  394. v-model="form.userId"
  395. placeholder="请选择责任人"
  396. clearable
  397. filterable
  398. disabled
  399. style="width: 100%"
  400. >
  401. <el-option
  402. v-for="user in userOptions"
  403. :key="user.userId"
  404. :label="user.nickName || user.userName"
  405. :value="user.userId"
  406. >
  407. <div style="display: flex; align-items: center;">
  408. <el-avatar size="small" style="margin-right: 8px">
  409. {{ (user.nickName || user.userName).charAt(0) }}
  410. </el-avatar>
  411. <span>{{ user.nickName || user.userName }}</span>
  412. </div>
  413. </el-option>
  414. </el-select>
  415. </el-form-item>
  416. <el-form-item label="附件上传" prop="annex">
  417. <file-upload
  418. v-model="form.annex"
  419. :on-remove="handleRemoveFile"
  420. :limit="5"
  421. :file-size="10"
  422. />
  423. </el-form-item>
  424. <el-form-item label="工单备注" prop="orderRemark">
  425. <el-input
  426. v-model="form.orderRemark"
  427. type="textarea"
  428. :rows="3"
  429. placeholder="请输入工单处理备注"
  430. maxlength="500"
  431. show-word-limit
  432. />
  433. </el-form-item>
  434. </el-form>
  435. <template #footer>
  436. <div class="dialog-footer">
  437. <el-button @click="cancel">取 消</el-button>
  438. <el-button type="primary" @click="submitForm" :loading="submitLoading">
  439. 确 定
  440. </el-button>
  441. </div>
  442. </template>
  443. </el-dialog>
  444. </div>
  445. </template>
  446. <script setup name="RepairOrder">
  447. import { ref, reactive, toRefs, onMounted, computed } from 'vue';
  448. import {
  449. listRepairOrder,
  450. getRepairOrder,
  451. delRepairOrder,
  452. addRepairOrder,
  453. updateRepairOrder,
  454. deleteFile, getRepairOrderStatistics
  455. } from "@/api/repairOrder/repairOrder";
  456. import { listUser } from "@/api/system/user";
  457. import { ElMessage, ElMessageBox } from 'element-plus';
  458. // 导入图标
  459. import {
  460. Tools,
  461. Plus,
  462. Download,
  463. DocumentCopy,
  464. Clock,
  465. Loading,
  466. CircleCheck,
  467. Search,
  468. Refresh,
  469. View,
  470. Hide,
  471. Edit,
  472. Delete,
  473. Paperclip
  474. } from '@element-plus/icons-vue';
  475. const { proxy } = getCurrentInstance();
  476. const { repair_status } = proxy.useDict('repair_status');
  477. // 响应式变量
  478. const repairOrderList = ref([]);
  479. const open = ref(false);
  480. const loading = ref(true);
  481. const submitLoading = ref(false);
  482. const showSearch = ref(true);
  483. const ids = ref([]);
  484. const single = ref(true);
  485. const multiple = ref(true);
  486. const total = ref(0);
  487. const title = ref("");
  488. const userOptions = ref([]);
  489. const detailVisible = ref(false);
  490. const detailData = ref({});
  491. const statisticsData = ref({
  492. total: 0,
  493. pending: 0,
  494. processing: 0,
  495. completed: 0
  496. });
  497. // 统计数据
  498. const statistics = computed(() => statisticsData.value);
  499. const data = reactive({
  500. form: {},
  501. queryParams: {
  502. isMe: true,
  503. pageNum: 1,
  504. pageSize: 10,
  505. orderNo: null,
  506. orderContent: null,
  507. projectName: null,
  508. orderStatus: null,
  509. dateRange: [],
  510. state: Date.now(),
  511. },
  512. rules: {
  513. finishTime: [
  514. { required: true, message: "完成时间不能为空", trigger: "blur" }
  515. ],
  516. orderRemark: [
  517. { required: true, message: "请填写处理备注", trigger: "blur" }
  518. ]
  519. }
  520. });
  521. const { queryParams, form, rules } = toRefs(data);
  522. // 获取状态标签类型
  523. function getStatusTagType(status) {
  524. const statusMap = {
  525. '0': 'warning',
  526. '1': 'primary',
  527. '2': 'success'
  528. };
  529. return statusMap[status] || 'info';
  530. }
  531. // 获取状态标签文本
  532. function getStatusLabel(status) {
  533. const dict = repair_status.value.find(item => item.value === status);
  534. return dict ? dict.label : '未知';
  535. }
  536. // 获取用户头像
  537. function getUserAvatar(userName) {
  538. // 这里可以根据实际情况返回用户头像URL
  539. return '';
  540. }
  541. // 表格行样式
  542. function tableRowClassName({ row }) {
  543. if (row.orderStatus === '0') {
  544. return 'warning-row';
  545. } else if (row.orderStatus === '2') {
  546. return 'success-row';
  547. }
  548. return '';
  549. }
  550. // 禁用日期(不能选择派单时间之前的日期)
  551. function disabledDate(time) {
  552. if (form.value.assignTime) {
  553. return time.getTime() < new Date(form.value.assignTime).getTime();
  554. }
  555. return false;
  556. }
  557. // 显示详情
  558. function showDetail(row) {
  559. detailData.value = { ...row };
  560. detailVisible.value = true;
  561. }
  562. // 下载文件
  563. function downloadFile(url) {
  564. if (!url) {
  565. ElMessage.warning('暂无附件');
  566. return;
  567. }
  568. window.open(import.meta.env.VITE_APP_BASE_API + url);
  569. }
  570. /** 查询维修工单列表 */
  571. async function getList() {
  572. loading.value = true;
  573. try {
  574. const params = { ...queryParams.value };
  575. // 处理日期范围
  576. /*if (dateRange.value && dateRange.value.length === 2) {
  577. params.startDate = dateRange.value[0];
  578. params.endDate = dateRange.value[1];
  579. }*/
  580. const response = await listRepairOrder(params);
  581. repairOrderList.value = response.rows;
  582. total.value = response.total;
  583. // 获取统计数据
  584. await getStatistics();
  585. } catch (error) {
  586. console.error('获取数据失败:', error);
  587. ElMessage.error('获取工单列表失败');
  588. } finally {
  589. loading.value = false;
  590. }
  591. }
  592. async function getStatistics() {
  593. try {
  594. // 如果后端没有统计接口,可以通过查询所有数据来统计
  595. const response = await getRepairOrderStatistics({isMe: true});
  596. // 直接更新 statisticsData
  597. statisticsData.value = {
  598. total: response.data.total || 0,
  599. pending: response.data.pending || 0,
  600. processing: response.data.processing || 0,
  601. completed: response.data.completed || 0
  602. };
  603. // 计算趋势(这里模拟数据,实际应该对比上期数据)
  604. statisticsData.value.forEach(stat => {
  605. stat.trend = Math.floor(Math.random() * 30) - 15; // 随机生成-15到15的趋势
  606. });
  607. } catch (error) {
  608. console.error('获取统计数据失败:', error);
  609. }
  610. }
  611. // 删除附件
  612. function handleRemoveFile(file) {
  613. ElMessageBox.confirm('确认删除该附件吗?', '提示', {
  614. confirmButtonText: '确定',
  615. cancelButtonText: '取消',
  616. type: 'warning'
  617. }).then(() => {
  618. deleteFile(file.url).then(() => {
  619. form.value.annex = null;
  620. ElMessage.success("删除成功");
  621. }).catch(() => {
  622. ElMessage.error("删除失败");
  623. });
  624. }).catch(() => {});
  625. }
  626. // 取消按钮
  627. function cancel() {
  628. open.value = false;
  629. reset();
  630. }
  631. // 表单重置
  632. function reset() {
  633. form.value = {
  634. id: null,
  635. orderNo: null,
  636. orderContent: null,
  637. projectName: null,
  638. assignTime: null,
  639. finishTime: null,
  640. finishBy: null,
  641. annex: null,
  642. userId: null,
  643. orderStatus: null,
  644. orderRemark: null,
  645. createBy: null,
  646. createTime: null,
  647. updateBy: null,
  648. updateTime: null
  649. };
  650. proxy.resetForm("repairOrderRef");
  651. }
  652. /** 搜索按钮操作 */
  653. function handleQuery() {
  654. queryParams.value.pageNum = 1;
  655. getList();
  656. }
  657. /** 重置按钮操作 */
  658. function resetQuery() {
  659. proxy.resetForm("queryRef");
  660. queryParams.value.dateRange = [];
  661. handleQuery();
  662. }
  663. // 多选框选中数据
  664. function handleSelectionChange(selection) {
  665. ids.value = selection.map(item => item.id);
  666. single.value = selection.length !== 1;
  667. multiple.value = !selection.length;
  668. }
  669. /** 新增按钮操作 */
  670. function handleAdd() {
  671. reset();
  672. open.value = true;
  673. title.value = "新建维修工单";
  674. }
  675. // 获取用户列表
  676. function getUserList() {
  677. listUser().then(response => {
  678. userOptions.value = response.rows || [];
  679. });
  680. }
  681. /** 修改按钮操作 */
  682. function handleUpdate(row) {
  683. reset();
  684. const _id = row.id || ids.value;
  685. getRepairOrder(_id).then(response => {
  686. form.value = response.data;
  687. open.value = true;
  688. title.value = "处理维修工单";
  689. });
  690. }
  691. /** 提交按钮 */
  692. function submitForm() {
  693. proxy.$refs["repairOrderRef"].validate(valid => {
  694. if (valid) {
  695. submitLoading.value = true;
  696. // 设置完成人信息
  697. if (form.value.userId) {
  698. const user = userOptions.value.find(u => u.userId === form.value.userId);
  699. if (user) {
  700. form.value.finishBy = user.nickName || user.userName;
  701. }
  702. }
  703. // 设置工单状态为已完成
  704. form.value.orderStatus = '2';
  705. if (form.value.id != null) {
  706. updateRepairOrder(form.value).then(response => {
  707. ElMessage.success("处理成功");
  708. open.value = false;
  709. getList();
  710. }).finally(() => {
  711. submitLoading.value = false;
  712. });
  713. } else {
  714. addRepairOrder(form.value).then(response => {
  715. ElMessage.success("新增成功");
  716. open.value = false;
  717. getList();
  718. }).finally(() => {
  719. submitLoading.value = false;
  720. });
  721. }
  722. }
  723. });
  724. }
  725. /** 删除按钮操作 */
  726. function handleDelete(row) {
  727. const _ids = row.id || ids.value;
  728. const deletePromises = [];
  729. ElMessageBox.confirm('是否确认删除选中的工单?删除后不可恢复!', '警告', {
  730. confirmButtonText: '确定',
  731. cancelButtonText: '取消',
  732. type: 'warning'
  733. }).then(() => {
  734. // 如果是批量删除,检查每个选中项是否有附件
  735. if (Array.isArray(ids.value) && ids.value.length > 0) {
  736. repairOrderList.value.forEach(item => {
  737. if (ids.value.includes(item.id) && item.annex) {
  738. deletePromises.push(deleteFile(item.annex));
  739. }
  740. });
  741. }
  742. // 如果是单个删除,检查是否有附件
  743. else if (row.annex) {
  744. deletePromises.push(deleteFile(row.annex));
  745. }
  746. // 先删除所有附件
  747. return Promise.all(deletePromises).then(() => {
  748. // 附件删除成功后删除数据
  749. return delRepairOrder(_ids);
  750. });
  751. }).then(() => {
  752. getList();
  753. ElMessage.success("删除成功");
  754. }).catch((error) => {
  755. if (error !== 'cancel') {
  756. ElMessage.error("删除失败");
  757. }
  758. });
  759. }
  760. /** 导出按钮操作 */
  761. function handleExport() {
  762. ElMessageBox.confirm('确定要导出所有维修工单数据吗?', '提示', {
  763. confirmButtonText: '确定',
  764. cancelButtonText: '取消',
  765. type: 'info'
  766. }).then(() => {
  767. proxy.download('repairOrder/repairOrder/export', {
  768. ...queryParams.value
  769. }, `维修工单_${new Date().getTime()}.xlsx`);
  770. });
  771. }
  772. // 初始化
  773. onMounted(() => {
  774. getList();
  775. getUserList();
  776. });
  777. </script>
  778. <style scoped lang="scss">
  779. .repair-order-container {
  780. padding: 20px;
  781. background-color: #f5f7fa;
  782. min-height: calc(100vh - 84px);
  783. .page-header {
  784. display: flex;
  785. justify-content: space-between;
  786. align-items: center;
  787. margin-bottom: 20px;
  788. .page-title {
  789. font-size: 24px;
  790. font-weight: 600;
  791. color: #303133;
  792. margin: 0;
  793. display: flex;
  794. align-items: center;
  795. .el-icon {
  796. margin-right: 8px;
  797. color: #409eff;
  798. }
  799. }
  800. .header-actions {
  801. display: flex;
  802. gap: 10px;
  803. }
  804. }
  805. // 统计卡片
  806. .statistics-cards {
  807. margin-bottom: 20px;
  808. .stat-card {
  809. background: #fff;
  810. border-radius: 8px;
  811. padding: 20px;
  812. display: flex;
  813. align-items: center;
  814. box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
  815. transition: all 0.3s;
  816. margin-bottom: 10px;
  817. &:hover {
  818. transform: translateY(-2px);
  819. box-shadow: 0 4px 20px 0 rgba(0, 0, 0, 0.08);
  820. }
  821. .stat-icon {
  822. width: 60px;
  823. height: 60px;
  824. border-radius: 12px;
  825. display: flex;
  826. align-items: center;
  827. justify-content: center;
  828. margin-right: 16px;
  829. .el-icon {
  830. font-size: 28px;
  831. }
  832. &.blue {
  833. background: #e6f2ff;
  834. color: #409eff;
  835. }
  836. &.orange {
  837. background: #fdf6ec;
  838. color: #e6a23c;
  839. }
  840. &.purple {
  841. background: #f3e8ff;
  842. color: #9c27b0;
  843. }
  844. &.green {
  845. background: #e8f5e9;
  846. color: #67c23a;
  847. }
  848. }
  849. .stat-content {
  850. flex: 1;
  851. .stat-value {
  852. font-size: 28px;
  853. font-weight: 600;
  854. color: #303133;
  855. line-height: 1;
  856. margin-bottom: 8px;
  857. }
  858. .stat-label {
  859. font-size: 14px;
  860. color: #909399;
  861. }
  862. }
  863. }
  864. }
  865. // 搜索卡片
  866. .search-card {
  867. margin-bottom: 20px;
  868. :deep(.el-card__body) {
  869. padding: 20px 20px 0;
  870. }
  871. .el-form-item {
  872. margin-bottom: 20px;
  873. }
  874. }
  875. // 表格卡片
  876. .table-card {
  877. :deep(.el-card__body) {
  878. padding: 0;
  879. }
  880. .table-header {
  881. display: flex;
  882. justify-content: space-between;
  883. align-items: center;
  884. padding: 20px;
  885. border-bottom: 1px solid #ebeef5;
  886. .table-title {
  887. font-size: 16px;
  888. font-weight: 600;
  889. color: #303133;
  890. }
  891. .table-tools {
  892. display: flex;
  893. gap: 8px;
  894. }
  895. }
  896. .el-table {
  897. :deep(.el-table__header) {
  898. th {
  899. background-color: #f5f7fa;
  900. color: #303133;
  901. font-weight: 600;
  902. }
  903. }
  904. :deep(.warning-row) {
  905. background-color: #fef0f0;
  906. }
  907. :deep(.success-row) {
  908. background-color: #f0f9ff;
  909. }
  910. .time-info {
  911. display: flex;
  912. align-items: center;
  913. justify-content: center;
  914. gap: 4px;
  915. color: #606266;
  916. .el-icon {
  917. color: #909399;
  918. }
  919. }
  920. .user-info {
  921. display: flex;
  922. align-items: center;
  923. justify-content: center;
  924. gap: 8px;
  925. .el-avatar {
  926. background-color: #409eff;
  927. color: #fff;
  928. font-size: 12px;
  929. }
  930. }
  931. .text-muted {
  932. color: #c0c4cc;
  933. }
  934. }
  935. }
  936. // 详情抽屉
  937. .detail-drawer {
  938. :deep(.el-drawer__header) {
  939. margin-bottom: 0;
  940. padding-bottom: 20px;
  941. border-bottom: 1px solid #ebeef5;
  942. }
  943. .drawer-header {
  944. display: flex;
  945. justify-content: space-between;
  946. align-items: center;
  947. width: 100%;
  948. h3 {
  949. margin: 0;
  950. font-size: 18px;
  951. color: #303133;
  952. }
  953. }
  954. .detail-content {
  955. padding: 20px;
  956. .detail-value {
  957. font-weight: 600;
  958. color: #409eff;
  959. }
  960. .attachment-section {
  961. margin-top: 30px;
  962. padding-top: 20px;
  963. border-top: 1px solid #ebeef5;
  964. h4 {
  965. margin: 0 0 15px 0;
  966. font-size: 16px;
  967. color: #303133;
  968. }
  969. }
  970. }
  971. }
  972. // 对话框样式
  973. :deep(.el-dialog) {
  974. .el-dialog__header {
  975. background-color: #f5f7fa;
  976. padding: 20px;
  977. .el-dialog__title {
  978. font-size: 18px;
  979. color: #303133;
  980. }
  981. }
  982. .el-dialog__body {
  983. padding: 20px;
  984. }
  985. .el-form {
  986. .el-form-item__label {
  987. font-weight: 500;
  988. }
  989. }
  990. }
  991. }
  992. // 响应式设计
  993. @media (max-width: 768px) {
  994. .repair-order-container {
  995. padding: 10px;
  996. .page-header {
  997. flex-direction: column;
  998. align-items: flex-start;
  999. gap: 10px;
  1000. .header-actions {
  1001. width: 100%;
  1002. display: flex;
  1003. justify-content: flex-end;
  1004. }
  1005. }
  1006. .statistics-cards {
  1007. .el-col {
  1008. margin-bottom: 10px;
  1009. }
  1010. }
  1011. .search-card {
  1012. :deep(.el-form) {
  1013. .el-form-item {
  1014. display: block;
  1015. margin-bottom: 16px;
  1016. }
  1017. }
  1018. }
  1019. .detail-drawer {
  1020. :deep(.el-drawer) {
  1021. width: 90% !important;
  1022. }
  1023. }
  1024. }
  1025. }
  1026. // 动画效果
  1027. @keyframes fadeIn {
  1028. from {
  1029. opacity: 0;
  1030. transform: translateY(20px);
  1031. }
  1032. to {
  1033. opacity: 1;
  1034. transform: translateY(0);
  1035. }
  1036. }
  1037. .stat-card {
  1038. animation: fadeIn 0.5s ease-out;
  1039. &:nth-child(1) { animation-delay: 0.1s; }
  1040. &:nth-child(2) { animation-delay: 0.2s; }
  1041. &:nth-child(3) { animation-delay: 0.3s; }
  1042. &:nth-child(4) { animation-delay: 0.4s; }
  1043. }
  1044. // 标签样式优化
  1045. :deep(.el-tag) {
  1046. .el-icon {
  1047. margin-right: 4px;
  1048. }
  1049. }
  1050. // 链接样式
  1051. .el-link {
  1052. font-weight: 500;
  1053. }
  1054. // 分页器样式
  1055. :deep(.pagination-container) {
  1056. padding: 20px;
  1057. background: #fff;
  1058. }
  1059. </style>