ncReportDialog.vue 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374
  1. <template>
  2. <u-popup
  3. :show="show"
  4. mode="bottom"
  5. :round="10"
  6. closeable
  7. @close="cancel"
  8. bgColor="#fff"
  9. :customStyle="{ maxHeight: '85vh', overflowY: 'auto' }"
  10. >
  11. <view class="nc_wrap">
  12. <view class="title">{{ title }}</view>
  13. <view class="section_title">报工信息</view>
  14. <view class="form_row">
  15. <view class="label required">实际开始时间</view>
  16. <view class="picker_box" @click="!isDetail && openTimePicker('realStartTime')">
  17. <text v-if="form.realStartTime" class="val">{{ form.realStartTime }}</text>
  18. <text v-else class="placeholder">请选择实际开始时间</text>
  19. </view>
  20. </view>
  21. <view class="form_row">
  22. <view class="label required">实际结束时间</view>
  23. <view class="picker_box" @click="!isDetail && openTimePicker('realEndTime')">
  24. <text v-if="form.realEndTime" class="val">{{ form.realEndTime }}</text>
  25. <text v-else class="placeholder">请选择实际结束时间</text>
  26. </view>
  27. </view>
  28. <view class="info_grid">
  29. <view class="info_item" v-for="item in fields" :key="item.prop">
  30. <view class="info_label">{{ item.label }}</view>
  31. <view class="info_val">{{ form[item.prop] || '-' }}</view>
  32. </view>
  33. </view>
  34. <view class="form_row" v-if="form.remark">
  35. <view class="label">订单备注</view>
  36. <view class="readonly_text">{{ form.remark }}</view>
  37. </view>
  38. <view class="form_row">
  39. <view class="label">附件</view>
  40. <view class="file_list">
  41. <view
  42. v-for="(f, idx) in form.fileParam"
  43. :key="(f && f.id) || idx"
  44. class="file_item"
  45. >
  46. <text class="file_name">{{ f.name || f.fileName || ("附件" + (idx + 1)) }}</text>
  47. <text v-if="!isDetail" class="file_del" @click="removeFile(idx)">删除</text>
  48. </view>
  49. <button v-if="!isDetail" class="upload_btn" @click="chooseFile">+ 上传附件</button>
  50. </view>
  51. </view>
  52. <view class="form_row">
  53. <view class="label">{{ isDetail ? '报备注' : '备注' }}</view>
  54. <textarea
  55. class="ipt textarea"
  56. v-model="form.reportRemark"
  57. :disabled="isDetail"
  58. placeholder="请输入"
  59. maxlength="-1"
  60. />
  61. </view>
  62. <view class="btns">
  63. <button class="btn cancel" @click="cancel">关闭</button>
  64. <button v-if="!isDetail" class="btn confirm" @click="submit">报工</button>
  65. </view>
  66. </view>
  67. <u-datetime-picker
  68. :show="timeShow"
  69. v-model="timeValue"
  70. mode="datetime"
  71. @confirm="onTimeConfirm"
  72. @cancel="timeShow = false"
  73. @close="timeShow = false"
  74. :closeOnClickOverlay="true"
  75. ></u-datetime-picker>
  76. </u-popup>
  77. </template>
  78. <script>
  79. import { ncTaskReport } from "@/api/pda/workReport.js";
  80. export default {
  81. data() {
  82. return {
  83. show: false,
  84. title: "NC任务报工",
  85. type: "",
  86. form: {
  87. realStartTime: "",
  88. realEndTime: "",
  89. brandNum: "",
  90. categoryName: "",
  91. categoryCode: "",
  92. code: "",
  93. productionPlanCode: "",
  94. produceRoutingName: "",
  95. taskName: "",
  96. modelType: "",
  97. specification: "",
  98. fileParam: [],
  99. remark: "",
  100. reportRemark: "",
  101. },
  102. fields: [
  103. { label: "牌号", prop: "brandNum" },
  104. { label: "名称", prop: "categoryName" },
  105. { label: "编码", prop: "categoryCode" },
  106. { label: "任务编号", prop: "code" },
  107. { label: "计划编号", prop: "productionPlanCode" },
  108. { label: "工艺路线", prop: "produceRoutingName" },
  109. { label: "工序名称", prop: "taskName" },
  110. { label: "型号", prop: "modelType" },
  111. { label: "规格", prop: "specification" },
  112. ],
  113. timeShow: false,
  114. timeField: "",
  115. timeValue: Date.now(),
  116. };
  117. },
  118. computed: {
  119. isDetail() {
  120. return this.type === "detail";
  121. },
  122. },
  123. methods: {
  124. open(item, type) {
  125. this.type = type || "";
  126. this.title = this.isDetail ? "NC任务详情" : "NC任务报工";
  127. this.form = {
  128. realStartTime: "",
  129. realEndTime: "",
  130. fileParam: [],
  131. remark: "",
  132. reportRemark: "",
  133. ...JSON.parse(JSON.stringify(item || {})),
  134. };
  135. this.show = true;
  136. },
  137. cancel() {
  138. this.show = false;
  139. },
  140. openTimePicker(field) {
  141. this.timeField = field;
  142. const cur = this.form[field];
  143. this.timeValue = cur ? new Date(cur.replace(/-/g, "/")).getTime() : Date.now();
  144. this.timeShow = true;
  145. },
  146. onTimeConfirm(e) {
  147. const d = new Date(e.value);
  148. const pad = (n) => String(n).padStart(2, "0");
  149. this.form[this.timeField] =
  150. d.getFullYear() +
  151. "-" +
  152. pad(d.getMonth() + 1) +
  153. "-" +
  154. pad(d.getDate()) +
  155. " " +
  156. pad(d.getHours()) +
  157. ":" +
  158. pad(d.getMinutes()) +
  159. ":" +
  160. pad(d.getSeconds());
  161. this.timeShow = false;
  162. },
  163. chooseFile() {
  164. const max = 9;
  165. const cur = this.form.fileParam || [];
  166. const remain = max - cur.length;
  167. if (remain <= 0) {
  168. return uni.showToast({ title: "最多上传9个附件", icon: "none" });
  169. }
  170. uni.chooseImage({
  171. count: remain,
  172. sizeType: ["compressed"],
  173. sourceType: ["camera", "album"],
  174. success: (res) => this.uploadFiles(res.tempFilePaths),
  175. });
  176. },
  177. uploadFiles(paths) {
  178. uni.showLoading({ title: "上传中..." });
  179. const tasks = paths.map(
  180. (p) =>
  181. new Promise((resolve, reject) => {
  182. uni.uploadFile({
  183. url: this.apiUrl + "/main/file/upload",
  184. filePath: p,
  185. name: "multiPartFile",
  186. header: { authorization: uni.getStorageSync("token") },
  187. success: (r) => {
  188. try {
  189. const data = JSON.parse(r.data);
  190. if (data.code == 0) resolve(data.data);
  191. else reject(data.message || "上传失败");
  192. } catch (e) {
  193. reject("上传失败");
  194. }
  195. },
  196. fail: () => reject("上传失败"),
  197. });
  198. }),
  199. );
  200. Promise.all(tasks)
  201. .then((arr) => {
  202. if (!this.form.fileParam) this.form.fileParam = [];
  203. this.form.fileParam.push(...arr);
  204. uni.hideLoading();
  205. uni.showToast({ title: "上传成功", icon: "success" });
  206. })
  207. .catch((err) => {
  208. uni.hideLoading();
  209. uni.showToast({ title: err || "上传失败", icon: "none" });
  210. });
  211. },
  212. removeFile(idx) {
  213. this.form.fileParam.splice(idx, 1);
  214. },
  215. submit() {
  216. if (!this.form.realStartTime)
  217. return uni.showToast({ title: "请选择实际开始时间", icon: "none" });
  218. if (!this.form.realEndTime)
  219. return uni.showToast({ title: "请选择实际结束时间", icon: "none" });
  220. if (!this.form.reportRemark)
  221. return uni.showToast({ title: "请输入备注", icon: "none" });
  222. const payload = {
  223. ...this.form,
  224. fileParam: (this.form.fileParam || []).map((f) =>
  225. f && typeof f === "object" ? { id: f.id } : { id: f },
  226. ),
  227. };
  228. ncTaskReport(payload)
  229. .then(() => {
  230. uni.showToast({ title: "报工成功", icon: "success" });
  231. this.$emit("refreshList");
  232. this.cancel();
  233. })
  234. .catch((err) => {
  235. uni.showToast({ title: err.message || "报工失败", icon: "none" });
  236. });
  237. },
  238. },
  239. };
  240. </script>
  241. <style lang="scss" scoped>
  242. .nc_wrap {
  243. padding: 30rpx;
  244. .title {
  245. font-size: 32rpx;
  246. font-weight: 600;
  247. text-align: center;
  248. margin-bottom: 20rpx;
  249. }
  250. .section_title {
  251. font-size: 28rpx;
  252. font-weight: 600;
  253. color: #333;
  254. margin: 20rpx 0 16rpx;
  255. padding-left: 16rpx;
  256. border-left: 6rpx solid $theme-color;
  257. }
  258. .form_row {
  259. margin-bottom: 20rpx;
  260. .label {
  261. font-size: 26rpx;
  262. color: #333;
  263. margin-bottom: 8rpx;
  264. &.required::before {
  265. content: "*";
  266. color: red;
  267. margin-right: 4rpx;
  268. }
  269. }
  270. .picker_box {
  271. border: 1rpx solid #e0e0e0;
  272. border-radius: 6rpx;
  273. padding: 14rpx 16rpx;
  274. font-size: 26rpx;
  275. min-height: 60rpx;
  276. .placeholder { color: #aaa; }
  277. }
  278. .ipt {
  279. border: 1rpx solid #e0e0e0;
  280. border-radius: 6rpx;
  281. padding: 14rpx 16rpx;
  282. font-size: 26rpx;
  283. width: 100%;
  284. box-sizing: border-box;
  285. }
  286. .textarea { min-height: 120rpx; }
  287. .readonly_text {
  288. padding: 14rpx 16rpx;
  289. background: #f7f9fa;
  290. border-radius: 6rpx;
  291. font-size: 26rpx;
  292. color: #666;
  293. line-height: 40rpx;
  294. word-break: break-all;
  295. }
  296. .file_list {
  297. display: flex;
  298. flex-wrap: wrap;
  299. gap: 12rpx;
  300. .file_item {
  301. display: flex;
  302. align-items: center;
  303. background: #f7f9fa;
  304. padding: 8rpx 14rpx;
  305. border-radius: 6rpx;
  306. font-size: 24rpx;
  307. max-width: 100%;
  308. .file_name {
  309. color: #333;
  310. word-break: break-all;
  311. margin-right: 10rpx;
  312. }
  313. .file_del {
  314. color: #f5222d;
  315. font-size: 22rpx;
  316. }
  317. }
  318. .upload_btn {
  319. height: 56rpx;
  320. line-height: 56rpx;
  321. padding: 0 22rpx;
  322. background: #fff;
  323. color: $theme-color;
  324. border: 1rpx dashed $theme-color;
  325. border-radius: 6rpx;
  326. font-size: 24rpx;
  327. margin: 0;
  328. }
  329. }
  330. }
  331. .info_grid {
  332. display: flex;
  333. flex-wrap: wrap;
  334. background: #f7f9fa;
  335. padding: 16rpx;
  336. border-radius: 8rpx;
  337. margin-bottom: 20rpx;
  338. .info_item {
  339. width: 50%;
  340. padding: 8rpx 6rpx;
  341. font-size: 24rpx;
  342. box-sizing: border-box;
  343. .info_label { color: #999; margin-bottom: 4rpx; }
  344. .info_val { color: #333; word-break: break-all; }
  345. }
  346. }
  347. .btns {
  348. display: flex;
  349. gap: 20rpx;
  350. margin-top: 30rpx;
  351. .btn {
  352. flex: 1;
  353. height: 80rpx;
  354. line-height: 80rpx;
  355. border-radius: 8rpx;
  356. font-size: 28rpx;
  357. }
  358. .cancel { background: #f5f5f5; color: #333; }
  359. .confirm { background: $theme-color; color: #fff; }
  360. }
  361. }
  362. </style>