| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505 |
- <template>
- <u-popup
- :show="show"
- mode="bottom"
- :round="10"
- :closeable="true"
- @close="cancel"
- bgColor="#fff"
- :customStyle="{ maxHeight: '85vh', overflowY: 'auto' }"
- >
- <view class="report_wrap">
- <view class="title">{{ title }}</view>
- <view class="section_title">报工信息</view>
- <view class="form_row">
- <view class="label required">实际开始时间</view>
- <picker
- mode="multiSelector"
- v-if="false"
- ></picker>
- <view class="picker_box" @click="!inputDis && openTimePicker('realStartTime')">
- <text v-if="form.realStartTime" class="val">{{ form.realStartTime }}</text>
- <text v-else class="placeholder">请选择实际开始时间</text>
- </view>
- </view>
- <view class="form_row">
- <view class="label required">实际结束时间</view>
- <view class="picker_box" @click="!inputDis && openTimePicker('realEndTime')">
- <text v-if="form.realEndTime" class="val">{{ form.realEndTime }}</text>
- <text v-else class="placeholder">请选择实际结束时间</text>
- </view>
- </view>
- <view class="form_row">
- <view class="label required">报工数</view>
- <input
- class="ipt"
- type="digit"
- v-model="form.reportQuantity"
- :disabled="inputDis"
- placeholder="请输入"
- @input="(e) => handleQuantityInput(e.detail.value, 'reportQuantity')"
- />
- </view>
- <view class="form_row">
- <view class="label required">损耗数</view>
- <input
- class="ipt"
- type="digit"
- v-model="form.lossQuantity"
- :disabled="inputDis"
- placeholder="请输入"
- @input="(e) => handleQuantityInput(e.detail.value, 'lossQuantity')"
- />
- </view>
- <view class="form_row">
- <view class="label">备注信息</view>
- <textarea
- class="ipt textarea"
- v-model="form.remark"
- :disabled="inputDis"
- placeholder="请输入"
- maxlength="-1"
- />
- </view>
- <view class="tip" v-if="!inputDis">
- 任务剩余可报数量:{{ remaining }}
- </view>
- <view class="section_title">工单信息</view>
- <view class="info_grid">
- <view class="info_item" v-for="item in fieldMap.orderList" :key="item.prop">
- <view class="info_label">{{ item.label }}</view>
- <view class="info_val">{{ current[item.prop] || '-' }}</view>
- </view>
- </view>
- <view class="section_title">任务信息</view>
- <view class="info_grid">
- <view class="info_item" v-for="item in fieldMap.taskList" :key="item.prop">
- <view class="info_label">{{ item.label }}</view>
- <view class="info_val">{{ current[item.prop] || '-' }}</view>
- </view>
- </view>
- <view v-if="title === '详情'" class="section_title">报工记录</view>
- <view v-if="title === '详情'" class="record_list">
- <view v-if="list.length === 0" class="empty">暂无记录</view>
- <view v-for="(rec, idx) in list" :key="idx" class="record_item">
- <view class="rec_row"><text class="rec_label">实际开始:</text>{{ rec.realStartTime || '-' }}</view>
- <view class="rec_row"><text class="rec_label">实际结束:</text>{{ rec.realEndTime || '-' }}</view>
- <view class="rec_row"><text class="rec_label">工时:</text>{{ rec.durationText || '-' }}</view>
- <view class="rec_row"><text class="rec_label">报工数:</text>{{ rec.reportQuantity || 0 }}</view>
- <view class="rec_row"><text class="rec_label">损耗数:</text>{{ rec.lossQuantity || 0 }}</view>
- <view class="rec_row"><text class="rec_label">备注:</text>{{ rec.remark || '-' }}</view>
- </view>
- </view>
- <view class="btns">
- <button class="btn cancel" @click="cancel">关闭</button>
- <button v-if="!inputDis" class="btn confirm" :disabled="loadingBtn" @click="submitAdd">报工</button>
- </view>
- </view>
- <u-datetime-picker
- :show="timeShow"
- v-model="timeValue"
- mode="datetime"
- @confirm="onTimeConfirm"
- @cancel="timeShow = false"
- @close="timeShow = false"
- :closeOnClickOverlay="true"
- ></u-datetime-picker>
- </u-popup>
- </template>
- <script>
- import {
- listWorkCenter,
- batchUpdateRealTime,
- listUpdateRealTimeRecord,
- } from "@/api/pda/workReport.js";
- export default {
- data() {
- return {
- show: false,
- title: "详情",
- timeShow: false,
- timeField: "",
- timeValue: Date.now(),
- form: {
- realEndTime: "",
- realStartTime: "",
- remark: "",
- reportQuantity: "",
- lossQuantity: "",
- workOrderCode: "",
- workOrderId: "",
- taskId: "",
- },
- current: {},
- list: [],
- reportNum: 0,
- lossNum: 0,
- loadingBtn: false,
- fieldMap: {
- orderList: [
- { label: "计划批次号", prop: "batchNo" },
- { label: "工单编码", prop: "mesWorkOrderCode" },
- { label: "产品编码", prop: "productCode" },
- { label: "产品名称", prop: "productName" },
- { label: "规格", prop: "specification" },
- { label: "生产订单编码", prop: "workOrderCode" },
- { label: "计划编号", prop: "productionPlanCode" },
- { label: "工艺路线", prop: "produceRoutingName" },
- { label: "要求生产数量", prop: "formingNum" },
- { label: "要求生产重量", prop: "formingWeight" },
- { label: "所属工厂", prop: "factoryName" },
- { label: "所属班组", prop: "assignTeamName" },
- { label: "计划开始时间", prop: "planStartTime" },
- { label: "计划结束时间", prop: "planCompleteTime" },
- ],
- taskList: [
- { label: "任务编码", prop: "assignCode" },
- { label: "工序名称", prop: "taskName" },
- { label: "执行类型", prop: "assigneeType" },
- { label: "执行对象", prop: "assigneeName" },
- { label: "工时", prop: "durationText" },
- { label: "任务开始时间", prop: "startTime" },
- { label: "任务结束时间", prop: "endTime" },
- { label: "任务数量", prop: "quantity" },
- { label: "任务重量", prop: "weight" },
- ],
- },
- };
- },
- computed: {
- inputDis() {
- return this.title === "详情";
- },
- remaining() {
- const req = Number(this.current.quantity) || 0;
- return Math.max(this.sub(req, this.add(this.reportNum, this.lossNum)), 0);
- },
- },
- methods: {
- open(type, row, form) {
- this.title = type === "report" ? "报工" : "详情";
- this.current = { ...row };
- this.list = [];
- if (form && form.realEndTime) {
- this.form = { ...form };
- } else {
- this.form = {
- realEndTime: form ? form.realEndTime || "" : "",
- realStartTime: form ? form.realStartTime || "" : "",
- remark: form ? form.remark || "" : "",
- reportQuantity: form ? form.reportQuantity : "",
- lossQuantity: form ? form.lossQuantity : 0,
- workOrderCode: form ? form.workOrderCode : "",
- workOrderId: form ? form.workOrderId : "",
- taskId: form ? form.taskId : "",
- };
- }
- this.reportNum = Number(this.current.reportQuantityReported) || 0;
- this.lossNum = Number(this.current.lossQuantityReported) || 0;
- if (type !== "report") {
- listUpdateRealTimeRecord(this.current.apsAssigneeId)
- .then((res) => {
- if (res) this.list = res;
- })
- .catch((err) => {
- uni.showToast({ title: err.message || "记录加载失败", icon: "none" });
- });
- }
- this.getFactory();
- this.show = true;
- },
- async getFactory() {
- if (!this.current.firstTaskId) return;
- try {
- const res = await listWorkCenter(this.current.firstTaskId);
- if (res && res.length) {
- this.$set(this.current, "factoryName", res[0].factoryName);
- }
- } catch (err) {
- // 忽略:仅展示字段
- }
- },
- cancel() {
- this.show = false;
- this.form = {
- realEndTime: "",
- realStartTime: "",
- remark: "",
- reportQuantity: "",
- lossQuantity: "",
- workOrderCode: "",
- workOrderId: "",
- taskId: "",
- };
- this.current = {};
- this.list = [];
- },
- openTimePicker(field) {
- this.timeField = field;
- const cur = this.form[field];
- this.timeValue = cur ? new Date(cur.replace(/-/g, "/")).getTime() : Date.now();
- this.timeShow = true;
- },
- onTimeConfirm(e) {
- const ts = e.value;
- const d = new Date(ts);
- const pad = (n) => String(n).padStart(2, "0");
- const val =
- d.getFullYear() +
- "-" +
- pad(d.getMonth() + 1) +
- "-" +
- pad(d.getDate()) +
- " " +
- pad(d.getHours()) +
- ":" +
- pad(d.getMinutes()) +
- ":" +
- pad(d.getSeconds());
- this.form[this.timeField] = val;
- this.timeShow = false;
- this.handleTimeChange();
- },
- handleTimeChange() {
- if (this.form.realStartTime && this.current.startTime) {
- const a = new Date(this.form.realStartTime.replace(/-/g, "/")).getTime();
- const b = new Date(String(this.current.startTime).replace(/-/g, "/")).getTime();
- if (a < b) {
- this.form.realStartTime = "";
- uni.showToast({ title: "实际开始时间不能早于任务开始时间", icon: "none" });
- return;
- }
- }
- if (this.form.realStartTime && this.form.realEndTime) {
- const a = new Date(this.form.realStartTime.replace(/-/g, "/")).getTime();
- const b = new Date(this.form.realEndTime.replace(/-/g, "/")).getTime();
- if (a > b) {
- this.form.realEndTime = "";
- uni.showToast({ title: "结束时间不能早于开始时间", icon: "none" });
- }
- }
- },
- handleQuantityInput(val, type) {
- let newVal = String(val || "").replace(/[^\d.]/g, "");
- if (newVal.startsWith(".")) newVal = "";
- const firstDot = newVal.indexOf(".");
- if (firstDot !== -1) {
- newVal =
- newVal.slice(0, firstDot + 1) +
- newVal.slice(firstDot + 1).replace(/\./g, "");
- }
- const match = newVal.match(/^(\d+)(\.\d{0,4})?/);
- this.form[type] = match ? match[0] : "";
- this.calculateQuantity(type);
- },
- calculateQuantity(type) {
- const curRep = Number(this.form.reportQuantity) || 0;
- const curLoss = Number(this.form.lossQuantity) || 0;
- if (this.add(curRep, curLoss) > this.remaining) {
- this.form[type] = "";
- uni.showToast({
- title: `本次报工数与损耗数之和不能超过任务剩余可报数量(剩余 ${this.remaining})`,
- icon: "none",
- });
- }
- },
- getDecimalLength(num) {
- return (num.toString().split(".")[1] || "").length;
- },
- toInteger(num) {
- const len = this.getDecimalLength(num);
- return { int: Math.round(num * Math.pow(10, len)), factor: Math.pow(10, len) };
- },
- add(a, b) {
- const { int: aInt, factor: aFactor } = this.toInteger(a);
- const { int: bInt, factor: bFactor } = this.toInteger(b);
- const maxFactor = Math.max(aFactor, bFactor);
- return (aInt * (maxFactor / aFactor) + bInt * (maxFactor / bFactor)) / maxFactor;
- },
- sub(a, b) {
- const { int: aInt, factor: aFactor } = this.toInteger(a);
- const { int: bInt, factor: bFactor } = this.toInteger(b);
- const maxFactor = Math.max(aFactor, bFactor);
- return (aInt * (maxFactor / aFactor) - bInt * (maxFactor / bFactor)) / maxFactor;
- },
- submitAdd() {
- if (!this.form.realStartTime) {
- return uni.showToast({ title: "请选择实际开始时间", icon: "none" });
- }
- if (!this.form.realEndTime) {
- return uni.showToast({ title: "请选择实际结束时间", icon: "none" });
- }
- if (this.form.reportQuantity === "" || this.form.reportQuantity === null) {
- return uni.showToast({ title: "请输入报工数量", icon: "none" });
- }
- if (this.form.lossQuantity === "" || this.form.lossQuantity === null) {
- return uni.showToast({ title: "请输入损耗数量", icon: "none" });
- }
- const curRep = Number(this.form.reportQuantity) || 0;
- const curLoss = Number(this.form.lossQuantity) || 0;
- if (this.add(curRep, curLoss) > this.remaining) {
- return uni.showToast({
- title: `本次报工数与损耗数之和不能超过任务剩余可报数量(剩余 ${this.remaining})`,
- icon: "none",
- });
- }
- const data = { ...this.form, apsAssigneeId: this.current.apsAssigneeId };
- this.loadingBtn = true;
- batchUpdateRealTime([data])
- .then(() => {
- this.loadingBtn = false;
- uni.showToast({ title: "操作成功", icon: "success" });
- this.$emit("success");
- this.cancel();
- })
- .catch((err) => {
- this.loadingBtn = false;
- uni.showToast({ title: err.message || "操作失败", icon: "none" });
- });
- },
- },
- };
- </script>
- <style lang="scss" scoped>
- .report_wrap {
- padding: 30rpx;
- .title {
- font-size: 32rpx;
- font-weight: 600;
- text-align: center;
- margin-bottom: 20rpx;
- }
- .section_title {
- font-size: 28rpx;
- font-weight: 600;
- color: #333;
- margin: 20rpx 0 16rpx;
- padding-left: 16rpx;
- border-left: 6rpx solid $theme-color;
- }
- .form_row {
- margin-bottom: 20rpx;
- .label {
- font-size: 26rpx;
- color: #333;
- margin-bottom: 8rpx;
- &.required::before {
- content: "*";
- color: red;
- margin-right: 4rpx;
- }
- }
- .picker_box {
- border: 1rpx solid #e0e0e0;
- border-radius: 6rpx;
- padding: 14rpx 16rpx;
- font-size: 26rpx;
- min-height: 60rpx;
- .placeholder {
- color: #aaa;
- }
- }
- .ipt {
- border: 1rpx solid #e0e0e0;
- border-radius: 6rpx;
- padding: 14rpx 16rpx;
- font-size: 26rpx;
- width: 100%;
- box-sizing: border-box;
- min-height: 72rpx;
- line-height: 44rpx;
- }
- .textarea {
- min-height: 120rpx;
- }
- }
- .tip {
- color: #ff9c00;
- font-size: 24rpx;
- margin: -8rpx 0 16rpx;
- }
- .info_grid {
- display: flex;
- flex-wrap: wrap;
- background: #f7f9fa;
- padding: 16rpx;
- border-radius: 8rpx;
- .info_item {
- width: 50%;
- padding: 8rpx 6rpx;
- font-size: 24rpx;
- box-sizing: border-box;
- .info_label {
- color: #999;
- margin-bottom: 4rpx;
- }
- .info_val {
- color: #333;
- word-break: break-all;
- }
- }
- }
- .record_list {
- .empty {
- text-align: center;
- color: #999;
- font-size: 26rpx;
- padding: 30rpx 0;
- }
- .record_item {
- background: #f7f9fa;
- padding: 16rpx;
- border-radius: 8rpx;
- margin-bottom: 12rpx;
- .rec_row {
- font-size: 24rpx;
- color: #333;
- line-height: 40rpx;
- .rec_label {
- color: #999;
- }
- }
- }
- }
- .btns {
- display: flex;
- gap: 20rpx;
- margin-top: 30rpx;
- position: sticky;
- bottom: 0;
- background: #fff;
- padding-top: 16rpx;
- .btn {
- flex: 1;
- height: 80rpx;
- line-height: 80rpx;
- border-radius: 8rpx;
- font-size: 28rpx;
- }
- .cancel {
- background: #f5f5f5;
- color: #333;
- }
- .confirm {
- background: $theme-color;
- color: #fff;
- }
- }
- }
- </style>
|