form.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515
  1. <script setup>
  2. import { ref, computed, watch, nextTick } from 'vue';
  3. import { onLoad } from '@dcloudio/uni-app';
  4. import { getRuiHenReportDetail } from '@/api/ruihen';
  5. import { useDataDictStore } from '@/store/modules/dataDict';
  6. const props = defineProps({
  7. type: {
  8. type: String,
  9. default: 'edit',
  10. },
  11. });
  12. const NON_PROD_FIELDS = [
  13. { key: 'repairTime', label: '设备故障' },
  14. { key: 'selfStopTime', label: '设备保养' },
  15. { key: 'accidentTime', label: '工程质量' },
  16. { key: 'complexityTime', label: '技术受限' },
  17. { key: 'rectificationTime', label: '生产组织' },
  18. { key: 'waitingStopTime', label: '不可抗力' },
  19. { key: 'partyaDesign', label: '甲方设计' },
  20. { key: 'partyaPrepare', label: '甲方准备' },
  21. { key: 'partyaResource', label: '甲方资源' },
  22. { key: 'relocationTime', label: '生产配合' },
  23. { key: 'winterBreakTime', label: '待命' },
  24. { key: 'otherNptTime', label: '其他非生产时间' },
  25. ];
  26. const FORM_KEYS = [
  27. 'id',
  28. 'deptId',
  29. 'projectId',
  30. 'taskId',
  31. 'deptName',
  32. 'contractName',
  33. 'taskName',
  34. 'dailyGasInjection',
  35. 'dailyWaterInjection',
  36. 'dailyInjectGasTime',
  37. 'dailyInjectWaterTime',
  38. 'productionStatus',
  39. 'remark',
  40. 'relocationDays',
  41. 'capacity',
  42. 'createTime',
  43. 'opinion',
  44. 'repairTime',
  45. 'selfStopTime',
  46. 'accidentTime',
  47. 'complexityTime',
  48. 'rectificationTime',
  49. 'waitingStopTime',
  50. 'partyaDesign',
  51. 'partyaPrepare',
  52. 'partyaResource',
  53. 'relocationTime',
  54. 'winterBreakTime',
  55. 'otherNptTime',
  56. 'otherNptReason',
  57. 'status',
  58. 'auditStatus',
  59. ];
  60. const formType = ref('edit');
  61. const initFormData = () => {
  62. const base = {
  63. dailyGasInjection: 0,
  64. dailyWaterInjection: 0,
  65. dailyInjectGasTime: 0,
  66. dailyInjectWaterTime: 0,
  67. relocationDays: 0,
  68. capacity: 0,
  69. };
  70. // 初始化所有非生产时间字段为 0
  71. NON_PROD_FIELDS.forEach(field => {
  72. base[field.key] = 0;
  73. });
  74. return base;
  75. };
  76. const form = ref(initFormData());
  77. async function loadDetail(id) {
  78. try {
  79. const { data } = await getRuiHenReportDetail({ id });
  80. form.value = initFormData();
  81. FORM_KEYS.forEach(key => {
  82. if (Object.prototype.hasOwnProperty.call(data, key) && data[key] !== null && data[key] !== undefined) {
  83. form.value[key] = data[key];
  84. }
  85. });
  86. form.value.id = id;
  87. if (props.type.includes('approval') && data.auditStatus !== 10) {
  88. formType.value = 'readonly';
  89. }
  90. if (props.type.includes('edit') && data.status !== 0) {
  91. formType.value = 'readonly';
  92. }
  93. if (props.type.includes('detail')) {
  94. formType.value = 'readonly';
  95. }
  96. if (!form.value.capacity) {
  97. uni.showToast({ title: '请维护增压机产能', icon: 'none' });
  98. }
  99. } finally {
  100. }
  101. }
  102. const dictStore = useDataDictStore();
  103. const nptReasonOptions = ref([]);
  104. const constructionStatusOptions = ref([]);
  105. const loadOptions = () => {
  106. nptReasonOptions.value = dictStore.getStrDictOptions('nptReason').map(v => ({
  107. text: v.label,
  108. value: v.value,
  109. }));
  110. constructionStatusOptions.value = dictStore.getStrDictOptions('constructionStatus').map(v => ({
  111. text: v.label,
  112. value: v.value,
  113. }));
  114. };
  115. onLoad(options => {
  116. if (dictStore.dataDict.length <= 0) {
  117. dictStore.loadDataDictList().then(() => {
  118. loadOptions();
  119. });
  120. } else loadOptions();
  121. loadDetail(options.id);
  122. });
  123. const defaultProps = computed(() => ({
  124. inputBorder: false,
  125. clearable: false,
  126. placeholder: '请输入',
  127. style: {
  128. 'text-align': 'right',
  129. },
  130. styles: {
  131. disableColor: '#fff',
  132. },
  133. }));
  134. const disabled = computed(() => field => {
  135. if (field === 'edit')
  136. return formType.value === 'readonly' || props.type.includes('approval') || props.type.includes('detail');
  137. else return formType.value === 'readonly';
  138. });
  139. const transitTime = computed(() => {
  140. const cap = form.value.capacity;
  141. const gas = form.value.dailyGasInjection ?? 0;
  142. if (!cap) return { original: 0, value: '0%' };
  143. const original = gas / cap;
  144. return { original, value: (original * 100).toFixed(2) + '%' };
  145. });
  146. const formRef = ref(null);
  147. // 辅助函数:计算总时间
  148. const sumNonProdTimes = () => {
  149. let sum = 0;
  150. NON_PROD_FIELDS.forEach(field => {
  151. sum += Number(form.value[field.key] || 0);
  152. });
  153. return sum;
  154. };
  155. const validateTotalTime = (rule, value, data, callback) => {
  156. const gasTime = Number(form.value.dailyInjectGasTime || 0);
  157. const waterTime = Number(form.value.dailyInjectWaterTime || 0);
  158. const nonProdSum = sumNonProdTimes();
  159. let total = 0;
  160. let msg = '';
  161. if (gasTime === 0 && waterTime > 0) {
  162. total = parseFloat((waterTime + nonProdSum).toFixed(2));
  163. msg = `注水(${waterTime})+非生产(${nonProdSum})=${total}H,必须为24H`;
  164. } else {
  165. total = parseFloat((gasTime + nonProdSum).toFixed(2));
  166. msg = `注气(${gasTime})+非生产(${nonProdSum})=${total}H,必须为24H`;
  167. }
  168. if (Math.abs(total - 24) > 0.01) {
  169. callback(msg);
  170. }
  171. return true;
  172. };
  173. // uni-forms 规则定义
  174. const rules = ref({
  175. dailyGasInjection: {
  176. rules: [{ required: true, errorMessage: '请输入当日注气量' }],
  177. },
  178. dailyWaterInjection: {
  179. rules: [{ required: true, errorMessage: '请输入当日注水量' }],
  180. },
  181. productionStatus: {
  182. rules: [{ required: true, errorMessage: '请输入生产动态' }],
  183. },
  184. constructionStatus: {
  185. rules: [{ required: true, errorMessage: '请选择施工状态' }],
  186. },
  187. dailyInjectGasTime: {
  188. rules: [{ required: true, errorMessage: '请输入当日注气量时间' }, { validateFunction: validateTotalTime }],
  189. },
  190. });
  191. const allTimeKeys = ['dailyInjectGasTime', 'dailyInjectWaterTime', ...NON_PROD_FIELDS.map(f => f.key)];
  192. watch(
  193. () => allTimeKeys.map(key => form.value[key]),
  194. () => {
  195. nextTick(() => {
  196. formRef.value?.validateField('dailyInjectGasTime');
  197. });
  198. },
  199. {
  200. deep: true,
  201. }
  202. );
  203. defineExpose({ formRef, form, loadDetail });
  204. </script>
  205. <template>
  206. <view class="content">
  207. <view class="tip">
  208. <view class="item">
  209. <view class="left">
  210. <span>运行时效:</span>
  211. 当日注气量 / 产能
  212. </view>
  213. <span class="right red"> >120% 红色预警 </span>
  214. </view>
  215. <!-- <view class="item">
  216. <view class="left">
  217. <span>时间平衡:</span>
  218. 注气 + 注水 + 非生产 = 24H
  219. </view>
  220. <span class="right orange"> ≠24H 橙色预警 </span>
  221. </view> -->
  222. </view>
  223. <view v-if="!type.includes('approval') && form.opinion" class="opinion">
  224. <span class="left">审批意见:</span>
  225. <span class="right"> {{ form.opinion }} </span>
  226. </view>
  227. <uni-forms
  228. ref="formRef"
  229. labelWidth="auto"
  230. :model="form"
  231. :rules="rules"
  232. validateTrigger="submit"
  233. err-show-type="toast">
  234. <uni-forms-item label="施工队伍" name="deptName">
  235. <span class="readOnly">{{ form.deptName }}</span>
  236. </uni-forms-item>
  237. <uni-forms-item label="项目" name="contractName">
  238. <span class="readOnly">{{ form.contractName }}</span>
  239. </uni-forms-item>
  240. <uni-forms-item label="任务" name="taskName">
  241. <span class="readOnly">{{ form.taskName }}</span>
  242. </uni-forms-item>
  243. <uni-forms-item label="搬迁安装天数(D)" name="relocationDays">
  244. <span class="readOnly">{{ form.relocationDays }}</span>
  245. </uni-forms-item>
  246. <uni-forms-item label="运行时效" name="transitTime">
  247. <span class="readOnly" :class="{ 'red-text': transitTime.original > 1.2 }">{{ transitTime.value }}</span>
  248. </uni-forms-item>
  249. <uni-forms-item label="施工状态" name="constructionStatus" required>
  250. <uni-data-select
  251. :clear="true"
  252. align="right"
  253. placeholder="请选择"
  254. :localdata="constructionStatusOptions"
  255. placement="top"
  256. :disabled="disabled('edit')"
  257. v-model="form.constructionStatus" />
  258. </uni-forms-item>
  259. <uni-forms-item label="当日注气量(方)" name="dailyGasInjection" required>
  260. <uni-easyinput
  261. type="number"
  262. v-bind="defaultProps"
  263. :disabled="disabled('edit')"
  264. v-model="form.dailyGasInjection" />
  265. </uni-forms-item>
  266. <uni-forms-item label="当日注水量(方)" name="dailyWaterInjection" required>
  267. <uni-easyinput
  268. type="number"
  269. v-bind="defaultProps"
  270. :disabled="disabled('edit')"
  271. v-model="form.dailyWaterInjection" />
  272. </uni-forms-item>
  273. <uni-forms-item label="生产动态" name="productionStatus" required>
  274. <uni-easyinput
  275. type="textarea"
  276. autoHeight
  277. v-bind="defaultProps"
  278. v-model="form.productionStatus"
  279. :disabled="disabled('edit')"
  280. :maxlength="1000" />
  281. </uni-forms-item>
  282. <uni-forms-item label="备注" name="remark">
  283. <uni-easyinput
  284. type="textarea"
  285. autoHeight
  286. v-bind="defaultProps"
  287. :disabled="disabled('edit')"
  288. v-model="form.remark"
  289. :maxlength="1000" />
  290. </uni-forms-item>
  291. <uv-divider text="生产时间" textPosition="left"></uv-divider>
  292. <uni-forms-item label="当日注气时间(H)" name="dailyInjectGasTime">
  293. <uni-easyinput
  294. type="number"
  295. v-bind="defaultProps"
  296. :disabled="disabled('edit')"
  297. v-model="form.dailyInjectGasTime" />
  298. </uni-forms-item>
  299. <uni-forms-item label="当日注水时间(H)" name="dailyInjectWaterTime">
  300. <uni-easyinput
  301. type="number"
  302. v-bind="defaultProps"
  303. :disabled="disabled('edit')"
  304. v-model="form.dailyInjectWaterTime" />
  305. </uni-forms-item>
  306. <uv-divider text="非生产时间" textPosition="left"></uv-divider>
  307. <uni-forms-item v-for="field in NON_PROD_FIELDS" :key="field.key" :label="field.label + '(H)'" :name="field.key">
  308. <uni-easyinput type="number" v-bind="defaultProps" :disabled="disabled('edit')" v-model="form[field.key]" />
  309. </uni-forms-item>
  310. <uni-forms-item label="其他非生产原因" name="otherNptReason">
  311. <uni-easyinput
  312. type="textarea"
  313. autoHeight
  314. v-bind="defaultProps"
  315. v-model="form.otherNptReason"
  316. :disabled="disabled('edit')"
  317. :maxlength="1000" />
  318. </uni-forms-item>
  319. <uni-forms-item v-if="type.includes('approval')" label="审批意见" name="opinion">
  320. <uni-easyinput
  321. type="textarea"
  322. autoHeight
  323. v-bind="defaultProps"
  324. :disabled="disabled('approval')"
  325. v-model="form.opinion"
  326. :maxlength="1000" />
  327. </uni-forms-item>
  328. </uni-forms>
  329. </view>
  330. </template>
  331. <style lang="scss" scoped>
  332. .content {
  333. background-color: white;
  334. padding: 16px 16px;
  335. border-radius: 8px;
  336. box-sizing: border-box;
  337. }
  338. .uni-forms {
  339. margin-top: 10px;
  340. height: 100%;
  341. .uni-form {
  342. height: 100%;
  343. }
  344. .uni-forms-item {
  345. display: flex;
  346. align-items: center;
  347. flex: 1;
  348. margin-bottom: 6px;
  349. border-bottom: 1px dashed #cacccf;
  350. }
  351. :deep(.uni-forms-item__content) {
  352. text-align: right;
  353. .readOnly {
  354. padding-right: 10px;
  355. }
  356. }
  357. :deep(.uni-forms-item__label) {
  358. height: 44px;
  359. font-weight: 500;
  360. font-size: 14px;
  361. color: #333333 !important;
  362. width: max-content !important;
  363. }
  364. :deep(.uni-select) {
  365. border: none;
  366. text-align: right;
  367. padding-right: 0;
  368. .uniui-bottom:before {
  369. content: '\e6b5' !important;
  370. font-size: 16px !important;
  371. }
  372. }
  373. :deep(.uni-easyinput__content-textarea) {
  374. min-height: inherit;
  375. margin: 10px;
  376. }
  377. :deep(.is-disabled) {
  378. color: #333333 !important;
  379. }
  380. :deep(.red-text > .is-disabled) {
  381. color: rgb(220 38 38 / 0.8) !important;
  382. }
  383. :deep(.orange-text > .is-disabled) {
  384. color: rgb(234 88 12 / 0.8) !important;
  385. }
  386. :deep(.uni-select--disabled) {
  387. background-color: #fff;
  388. }
  389. }
  390. .red-text {
  391. color: rgb(220 38 38 / 0.8) !important;
  392. }
  393. .orange-text {
  394. color: rgb(234 88 12 / 0.8) !important;
  395. }
  396. .red {
  397. border: 1px solid rgb(254 226 226);
  398. color: rgb(220 38 38 / 0.8);
  399. background-color: rgb(254 226 226);
  400. }
  401. .orange {
  402. border: 1px solid rgb(254 215 170);
  403. color: rgb(234 88 12 / 0.8);
  404. background-color: rgb(254 215 170);
  405. }
  406. .tip {
  407. border-radius: 8px;
  408. border: 1px solid #e5e5e5;
  409. background-color: rgba(239, 246, 255, 0.8);
  410. box-sizing: border-box;
  411. padding: 10px;
  412. display: flex;
  413. flex-direction: column;
  414. gap: 6px;
  415. .item {
  416. display: flex;
  417. align-items: center;
  418. justify-content: space-between;
  419. font-size: 12px;
  420. .left {
  421. color: rgb(75 85 99);
  422. span {
  423. color: rgb(31 41 55);
  424. font-weight: 600;
  425. }
  426. }
  427. .right {
  428. display: inline-flex;
  429. align-items: center;
  430. border-radius: 4px;
  431. padding: 2px 4px;
  432. font-weight: 500;
  433. }
  434. }
  435. }
  436. .opinion {
  437. border-radius: 8px;
  438. border: 1px solid rgb(254 240 138);
  439. background-color: rgb(254 252 232);
  440. box-sizing: border-box;
  441. padding: 10px;
  442. display: flex;
  443. align-items: center;
  444. justify-content: space-between;
  445. font-size: 12px;
  446. margin-top: 10px;
  447. .left {
  448. font-weight: 600;
  449. color: rgb(133 77 14);
  450. }
  451. .right {
  452. font-weight: 500;
  453. color: rgb(75 85 99);
  454. }
  455. }
  456. </style>