WriteOrder.vue 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342
  1. <template>
  2. <ContentWrap>
  3. <el-tabs
  4. v-model="activeTab"
  5. type="border-card"
  6. tab-position="left"
  7. v-loading="loading"
  8. style="height: 84vh"
  9. >
  10. <el-tab-pane
  11. style="height: 100%"
  12. v-for="(tab, tabIndex) in tabs"
  13. :key="tab.deviceName"
  14. :name="String(tabIndex)"
  15. >
  16. <template #label>
  17. <span class="custom-label">
  18. {{ tab.deviceName }} ({{ completedSteps(tabIndex) }}/{{ tab.orderDetails.length }})
  19. </span>
  20. </template>
  21. <div class="step-container">
  22. <el-steps direction="vertical" :active="currentStep[tabIndex]" class="steps-nav">
  23. <el-step
  24. v-for="(step, stepIndex) in tab.orderDetails"
  25. :key="stepIndex"
  26. :title="`${step.item}`"
  27. :status="getStepStatus(tabIndex, stepIndex)"
  28. >
  29. <template #title>
  30. <div class="step-title-wrapper">
  31. <span class="title-text">{{ step.item }}</span>
  32. <el-tooltip :content="step.standard" placement="top" effect="dark">
  33. <Icon icon="ep:info-filled" />
  34. </el-tooltip>
  35. </div>
  36. </template>
  37. </el-step>
  38. </el-steps>
  39. <div class="form-wrapper">
  40. <ContentWrap>
  41. <el-form
  42. :model="formData[tabIndex][currentStep[tabIndex]]"
  43. :rules="formRules"
  44. ref="formRefs"
  45. label-width="120px"
  46. >
  47. <el-form-item label="设备id" v-if="false" prop="deviceId">
  48. <el-input
  49. v-model="formData[tabIndex][currentStep[tabIndex]].deviceId"
  50. :model-value="tab.deviceId"
  51. clearable
  52. />
  53. </el-form-item>
  54. <el-form-item label="是否异常" prop="ifNormal">
  55. <el-select
  56. v-model="formData[tabIndex][currentStep[tabIndex]].ifNormal"
  57. placeholder="请选择是否异常"
  58. clearable
  59. >
  60. <el-option
  61. v-for="dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)"
  62. :key="dict.value"
  63. :label="dict.label"
  64. :value="dict.value"
  65. />
  66. </el-select>
  67. </el-form-item>
  68. <el-form-item
  69. label="异常描述"
  70. prop="description"
  71. :rules="formData[tabIndex][currentStep[tabIndex]].ifNormal ? descriptionRule : []"
  72. >
  73. <el-input
  74. v-model="formData[tabIndex][currentStep[tabIndex]].description"
  75. type="textarea"
  76. :rows="5"
  77. placeholder="请填写异常描述"
  78. />
  79. </el-form-item>
  80. <el-form-item label="图片" prop="picUrl">
  81. <UploadImg
  82. v-model="formData[tabIndex][currentStep[tabIndex]].picUrl"
  83. height="55px"
  84. width="30em"
  85. />
  86. </el-form-item>
  87. </el-form>
  88. <div class="navigation-controls">
  89. <el-button :disabled="currentStep[tabIndex] === 0" @click="handlePrev(tabIndex)">
  90. 上一步
  91. </el-button>
  92. <el-button
  93. type="primary"
  94. :disabled="!isStepValid(tabIndex)"
  95. @click="handleNext(tabIndex, tab.deviceId)"
  96. >
  97. {{ isLastStep(tabIndex) ? '完成提交' : '下一步' }}
  98. </el-button>
  99. </div>
  100. </ContentWrap>
  101. <ContentWrap style="margin-top: 50px">
  102. <el-card>
  103. <span style="font-weight: bold">巡检标准:</span
  104. >{{ tab.orderDetails[currentStep[tabIndex]].standard }}<br />
  105. <el-divider />
  106. <span style="font-weight: bold">附件:</span>
  107. <el-button
  108. v-if="tab.orderDetails[currentStep[tabIndex]].urls"
  109. link
  110. type="primary"
  111. @click="openWeb(tab.orderDetails[currentStep[tabIndex]].urls)"
  112. >
  113. <Icon size="19" icon="ep:view" />预览
  114. </el-button>
  115. </el-card>
  116. </ContentWrap>
  117. </div>
  118. </div>
  119. </el-tab-pane>
  120. </el-tabs>
  121. </ContentWrap>
  122. </template>
  123. <script setup>
  124. import { useTagsViewStore } from '@/store/modules/tagsView'
  125. import { onMounted, reactive, ref } from 'vue'
  126. import { ElMessage } from 'element-plus'
  127. import { IotInspectOrderApi } from '@/api/pms/inspect/order'
  128. import { DICT_TYPE, getBoolDictOptions } from '@/utils/dict'
  129. defineOptions({ name: 'InspectOrderWrite' })
  130. const { t } = useI18n() // 国际化
  131. const { delView } = useTagsViewStore() // 视图操作
  132. const { currentRoute, push } = useRouter()
  133. const message = useMessage() // 消息弹窗
  134. const tabs = ref([])
  135. const { params, name } = useRoute() // 查询参数
  136. const id = params.id
  137. onMounted(async () => {
  138. if (id) {
  139. const iotInspectOrderDetail = await IotInspectOrderApi.getIotInspectOrderDetail(id)
  140. tabs.value = iotInspectOrderDetail
  141. initFormData(iotInspectOrderDetail)
  142. loading.value = false
  143. }
  144. })
  145. const openWeb = (url) => {
  146. window.open(
  147. 'http://1.94.244.160:8012/onlinePreview?url=' + encodeURIComponent(Base64.encode(url))
  148. )
  149. }
  150. // 响应式状态
  151. const loading = ref(true)
  152. const activeTab = ref('0')
  153. const currentStep = ref({})
  154. const formRefs = ref([])
  155. const formData = reactive([])
  156. const descriptionRule = [{ required: true, message: '描述不能为空', trigger: 'blur' }]
  157. // 表单验证规则
  158. const formRules = reactive({
  159. ifNormal: [{ required: true, message: '请输入是否异常', trigger: 'blur' }]
  160. // description: [
  161. // { required: true, message: '请输入异常描述', trigger: 'blur' },
  162. // ],
  163. })
  164. // 初始化表单数据
  165. const initFormData = (tabsData) => {
  166. tabsData.forEach((tab, tabIndex) => {
  167. formData[tabIndex] = tab.orderDetails.map((item) => ({
  168. deviceId: item.deviceId,
  169. indexId: item.indexId,
  170. ifNormal: item.ifNormal,
  171. description: item.description,
  172. picUrl: item.picUrl
  173. }))
  174. currentStep.value[tabIndex] = 0
  175. })
  176. }
  177. // 步骤状态计算
  178. const getStepStatus = (tabIndex, stepIndex) => {
  179. if (stepIndex < currentStep.value[tabIndex]) return 'finish'
  180. return stepIndex === currentStep.value[tabIndex] ? 'process' : 'wait'
  181. }
  182. // 完成步骤数计算
  183. const completedSteps = (tabIndex) => {
  184. return formData[tabIndex].filter((item) => item.ifNormal != null).length
  185. }
  186. // 步骤验证
  187. const isStepValid = (tabIndex) => {
  188. const current = currentStep.value[tabIndex]
  189. debugger
  190. if (
  191. formData[tabIndex][current].ifNormal === null ||
  192. formData[tabIndex][current].ifNormal === undefined
  193. ) {
  194. return false
  195. }
  196. if (
  197. formData[tabIndex][current].ifNormal &&
  198. (formData[tabIndex][current].description === undefined ||
  199. formData[tabIndex][current].description === null ||
  200. formData[tabIndex][current].description === '')
  201. ) {
  202. return false
  203. }
  204. return true
  205. }
  206. // 导航控制
  207. const handlePrev = (tabIndex) => {
  208. if (currentStep.value[tabIndex] > 0) {
  209. currentStep.value[tabIndex]--
  210. }
  211. }
  212. const handleNext = (tabIndex, deviceId) => {
  213. if (!isStepValid(tabIndex)) {
  214. return ElMessage.warning('请先完成当前步骤的必填项')
  215. }
  216. const current = currentStep.value[tabIndex]
  217. const totalSteps = tabs.value[tabIndex].orderDetails.length
  218. debugger
  219. if (currentStep.value[tabIndex] < totalSteps - 1) {
  220. formData[tabIndex][current].indexId = current + 1
  221. formData[tabIndex][current].deviceId = deviceId
  222. currentStep.value[tabIndex]++
  223. submitForm(tabIndex, current, '')
  224. } else {
  225. formData[tabIndex][current].indexId = current + 1
  226. formData[tabIndex][current].deviceId = deviceId
  227. submitForm(tabIndex, current, 'finish')
  228. }
  229. }
  230. const isLastStep = (tabIndex) => {
  231. debugger
  232. return currentStep.value[tabIndex] === tabs.value[tabIndex].orderDetails.length - 1
  233. }
  234. // 提交表单
  235. const submitForm = (tabIndex, current, type) => {
  236. try {
  237. IotInspectOrderApi.writeIotInspectOrder(formData[tabIndex][current], id)
  238. if (type === 'finish') {
  239. message.success(t('common.createSuccess'))
  240. delView(unref(currentRoute))
  241. push({ name: 'IotInspectOrder', params: {} })
  242. }
  243. } catch (error) {
  244. debugger
  245. ElMessage.error('提交失败,请检查数据')
  246. }
  247. }
  248. </script>
  249. <style scoped>
  250. .step-container {
  251. display: grid;
  252. grid-template-columns: 220px 1fr;
  253. gap: 10px;
  254. height: 100%;
  255. min-height: 600px;
  256. }
  257. .steps-nav {
  258. overflow-y: auto;
  259. padding-right: 15px;
  260. }
  261. .form-wrapper {
  262. background: #fff;
  263. padding: 30px;
  264. border-radius: 8px;
  265. box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
  266. }
  267. .navigation-controls {
  268. margin-top: 40px;
  269. text-align: center;
  270. }
  271. .custom-label {
  272. font-weight: 1000;
  273. font-size: 17px;
  274. padding: 0 10px;
  275. }
  276. ::v-deep .el-step__icon {
  277. background-color: #409eff;
  278. color: #fff;
  279. border: 0px;
  280. }
  281. .step-title-wrapper {
  282. display: inline-flex;
  283. align-items: center;
  284. gap: 8px;
  285. position: relative;
  286. padding-right: 25px;
  287. }
  288. /* 覆盖步骤条默认样式 */
  289. :deep(.custom-steps) {
  290. /* 调整头部位置 */
  291. .el-step__head {
  292. top: 3px;
  293. }
  294. /* 标题容器定位 */
  295. .el-step__title {
  296. display: inline-block;
  297. margin-left: 10px;
  298. padding-right: 0;
  299. }
  300. /* 步骤连接线 */
  301. .el-step__line {
  302. left: 11px;
  303. background-color: #ebeef5;
  304. }
  305. /* 当前步骤样式 */
  306. .is-process .title-text {
  307. font-weight: 600;
  308. color: #409eff;
  309. }
  310. /* 完成状态图标 */
  311. .is-finish .tip-icon {
  312. color: #67c23a;
  313. }
  314. }
  315. </style>