| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import request from '@/utils/request' |
| | | |
| | | // æ¥è¯¢å¨èåå·¥å°è´¦ |
| | | export function staffOnJobListPage(query) { |
| | | return request({ |
| | | url: '/staff/staffOnJob/listPage', |
| | | method: 'get', |
| | | params: query, |
| | | }) |
| | | } |
| | | // æ¥è¯¢åå·¥å
¥èä¿¡æ¯ |
| | | export function staffOnJobInfo(id, query) { |
| | | return request({ |
| | | url: '/staff/staffOnJob/' + id, |
| | | method: 'get', |
| | | params: query, |
| | | }) |
| | | } |
| | | |
| | | // æ¥è¯¢åå·¥å
¥èä¿¡æ¯ |
| | | export function getStaffOnJobInfoByUserName(query) { |
| | | return request({ |
| | | url: '/staff/staffOnJob/byUserName', |
| | | method: 'get', |
| | | params: query, |
| | | }) |
| | | } |
| | | |
| | | // æ°å¢åå·¥ |
| | | export function createStaffOnJob(params) { |
| | | return request({ |
| | | url: "/staff/staffOnJob", |
| | | method: "post", |
| | | data: params, |
| | | }); |
| | | } |
| | | |
| | | // ä¿®æ¹åå·¥ |
| | | export function updateStaffOnJob(id, params) { |
| | | return request({ |
| | | url: "/staff/staffOnJob/" + id, |
| | | method: "put", |
| | | data: params, |
| | | }); |
| | | } |
| | | |
| | | // å é¤åå·¥ |
| | | export function batchDeleteStaffOnJobs(query) { |
| | | return request({ |
| | | url: "/staff/staffOnJob/del", |
| | | method: "delete", |
| | | data: query, |
| | | }); |
| | | } |
| | | |
| | | // ç»ç¾åå |
| | | export function renewContract(id, params) { |
| | | return request({ |
| | | url: "/staff/staffOnJob/renewContract/" + id, |
| | | method: "post", |
| | | data: params, |
| | | }); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | // å·¥åºé¡µé¢æ¥å£ |
| | | import request from "@/utils/request"; |
| | | |
| | | // å页æ¥è¯¢ |
| | | export function listPage(query) { |
| | | return request({ |
| | | url: "/technologyOperation/listPage", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | |
| | | export function processList(query) { |
| | | return request({ |
| | | url: "/technologyOperation/listPage", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | |
| | | // å·¥åºæ¥è¯¢ |
| | | export function list(query) { |
| | | return request({ |
| | | url: "/technologyOperation/listPage", |
| | | method: "get", |
| | | params: query, |
| | | }); |
| | | } |
| | | export function add(data) { |
| | | return request({ |
| | | url: "/technologyOperation/add", |
| | | method: "post", |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | export function del(data) { |
| | | return request({ |
| | | url: "/technologyOperation/batchDelete", |
| | | method: "delete", |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | export function update(data) { |
| | | return request({ |
| | | url: "/technologyOperation/update", |
| | | method: "put", |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | // 导å
¥æ°æ® |
| | | export function importData(data) { |
| | | return request({ |
| | | url: "/technologyOperation/importData", |
| | | method: "post", |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | // ä¸è½½æ¨¡æ¿ |
| | | export function downloadTemplate() { |
| | | return request({ |
| | | url: "/technologyOperation/downloadTemplate", |
| | | method: "post", |
| | | responseType: "blob", |
| | | }); |
| | | } |
| | | |
| | | // è·åå·¥åºåæ°å表 |
| | | export function getProcessParamList(params) { |
| | | return request({ |
| | | url: `/technologyOperationParam/list`, |
| | | method: "get", |
| | | params, |
| | | }); |
| | | } |
| | | |
| | | // æ·»å å·¥åºåæ° |
| | | export function addProcessParam(data) { |
| | | return request({ |
| | | url: "/technologyOperationParam/", |
| | | method: "post", |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | // ç¼è¾å·¥åºåæ° |
| | | export function editProcessParam(data) { |
| | | return request({ |
| | | url: "/technologyOperationParam/", |
| | | method: "post", |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | // å é¤å·¥åºåæ° |
| | | export function deleteProcessParam(id) { |
| | | return request({ |
| | | url: `/technologyOperationParam/batchDelete/${id}`, |
| | | method: "delete", |
| | | }); |
| | | } |
| | |
| | | }); |
| | | } |
| | | |
| | | // å¼å§æ¥å·¥ |
| | | export function startWork(data) { |
| | | return request({ |
| | | url: "/productionProductMain/startWork", |
| | | method: "post", |
| | | data: data, |
| | | }); |
| | | } |
| | | |
| | | // è·åå·¥åºç»è®¡æ°æ® |
| | | export function getOperationStatistics(query) { |
| | | return request({ |
| | |
| | | // åºç¨å
¨å±é
ç½® |
| | | const config = { |
| | | baseUrl: "http://1.15.17.182:9048", |
| | | fileUrl: "http://1.15.17.182:9049", |
| | | baseUrl: "http://1.15.17.182:9098", |
| | | fileUrl: "http://1.15.17.182:9097", |
| | | // åºç¨ä¿¡æ¯ |
| | | appInfo: { |
| | | // åºç¨åç§° |
| | |
| | | :model="form" |
| | | :rules="rules" |
| | | :errorType="['none']" |
| | | label-width="110"> |
| | | <up-form-item label="å·¥åºç¼ç " |
| | | prop="no"> |
| | | <up-input v-model="form.no" |
| | | placeholder="请è¾å
¥å·¥åºç¼ç " |
| | | clearable /> |
| | | </up-form-item> |
| | | <up-form-item label="å·¥åºåç§°" |
| | | label-width="130"> |
| | | <up-form-item label="é¨ä»¶" |
| | | prop="name" |
| | | required> |
| | | <up-input v-model="form.name" |
| | | placeholder="请è¾å
¥å·¥åºåç§°" |
| | | placeholder="请è¾å
¥é¨ä»¶" |
| | | clearable /> |
| | | </up-form-item> |
| | | <up-form-item label="å·¥èµå®é¢" |
| | | <up-form-item label="å·¥åºç¼å·" |
| | | prop="no"> |
| | | <up-input v-model="form.no" |
| | | placeholder="请è¾å
¥å·¥åºç¼å·" |
| | | clearable /> |
| | | </up-form-item> |
| | | <up-form-item label="å·¥åºç±»å" |
| | | prop="processType" |
| | | required> |
| | | <up-input v-model="processTypeText" |
| | | placeholder="è¯·éæ©å·¥åºç±»å" |
| | | readonly |
| | | @click="showProcessTypeSheet = true" /> |
| | | <template #right> |
| | | <up-icon name="arrow-right" |
| | | @click="showProcessTypeSheet = true"></up-icon> |
| | | </template> |
| | | </up-form-item> |
| | | <up-form-item label="计åå·¥æ¶(å°æ¶)" |
| | | prop="salaryQuota"> |
| | | <up-input v-model="form.salaryQuota" |
| | | type="number" |
| | | placeholder="请è¾å
¥å·¥èµå®é¢" |
| | | placeholder="请è¾å
¥è®¡åå·¥æ¶" |
| | | clearable /> |
| | | </up-form-item> |
| | | <up-form-item label="计费类å" |
| | | prop="type"> |
| | | <up-input v-model="typeText" |
| | | placeholder="è¯·éæ©è®¡è´¹ç±»å" |
| | | <up-form-item label="计å人å" |
| | | prop="planPerson"> |
| | | <up-input v-model="planPersonText" |
| | | placeholder="è¯·éæ©è®¡å人å" |
| | | readonly |
| | | @click="showTypeSheet = true" /> |
| | | @click="showPlanPersonSheet = true" /> |
| | | <template #right> |
| | | <up-icon name="arrow-right" |
| | | @click="showTypeSheet = true"></up-icon> |
| | | @click="showPlanPersonSheet = true"></up-icon> |
| | | </template> |
| | | </up-form-item> |
| | | <up-form-item label="è®¡åæ§è¡äººå" |
| | | prop="executor"> |
| | | <up-input v-model="executorText" |
| | | placeholder="è¯·éæ©è®¡åæ§è¡äººå" |
| | | readonly |
| | | @click="showExecutorSheet = true" /> |
| | | <template #right> |
| | | <up-icon name="arrow-right" |
| | | @click="showExecutorSheet = true"></up-icon> |
| | | </template> |
| | | </up-form-item> |
| | | <up-form-item label="æ¯å¦è´¨æ£" |
| | |
| | | <up-switch v-model="form.isQuality" /> |
| | | </view> |
| | | </up-form-item> |
| | | <up-form-item label="æ¯å¦ç产" |
| | | prop="isProduction"> |
| | | <up-form-item label="æ¯å¦å
¥åº" |
| | | prop="inbound"> |
| | | <view style="display: flex; justify-content: flex-end; width: 100%;"> |
| | | <up-switch v-model="form.isProduction" /> |
| | | <up-switch v-model="form.inbound" /> |
| | | </view> |
| | | </up-form-item> |
| | | <up-form-item label="å
³è设å¤" |
| | | prop="deviceLedgerId"> |
| | | <up-input v-model="deviceText" |
| | | placeholder="è¯·éæ©å
³è设å¤" |
| | | readonly |
| | | @click="showDeviceSheet = true" /> |
| | | <template #right> |
| | | <up-icon name="arrow-right" |
| | | @click="showDeviceSheet = true"></up-icon> |
| | | </template> |
| | | <up-form-item label="æ¯å¦æ¥å·¥" |
| | | prop="reportWork"> |
| | | <view style="display: flex; justify-content: flex-end; width: 100%;"> |
| | | <up-switch v-model="form.reportWork" /> |
| | | </view> |
| | | </up-form-item> |
| | | <up-form-item label="å·¥åºæè¿°" |
| | | <up-form-item label="夿³¨" |
| | | prop="remark"> |
| | | <up-textarea v-model="form.remark" |
| | | placeholder="请è¾å
¥å·¥åºæè¿°" |
| | | placeholder="请è¾å
¥å¤æ³¨" |
| | | autoHeight /> |
| | | </up-form-item> |
| | | </up-form> |
| | |
| | | :confirmText="processId ? 'ä¿å' : 'æ°å¢'" |
| | | @cancel="goBack" |
| | | @confirm="handleSubmit" /> |
| | | <!-- 计费类åéæ© --> |
| | | <up-action-sheet :show="showTypeSheet" |
| | | title="éæ©è®¡è´¹ç±»å" |
| | | :actions="typeActions" |
| | | @select="onSelectType" |
| | | @close="showTypeSheet = false" /> |
| | | <!-- 设å¤éæ© --> |
| | | <up-action-sheet :show="showDeviceSheet" |
| | | title="éæ©å
³è设å¤" |
| | | :actions="deviceActions" |
| | | @select="onSelectDevice" |
| | | @close="showDeviceSheet = false" /> |
| | | <!-- å·¥åºç±»åéæ© --> |
| | | <up-action-sheet :show="showProcessTypeSheet" |
| | | title="鿩工åºç±»å" |
| | | :actions="processTypeActions" |
| | | @select="onSelectProcessType" |
| | | @close="showProcessTypeSheet = false" /> |
| | | <!-- 计å人åéæ© --> |
| | | <up-action-sheet :show="showPlanPersonSheet" |
| | | title="éæ©è®¡å人å" |
| | | :actions="employeeActions" |
| | | @select="onSelectPlanPerson" |
| | | @close="showPlanPersonSheet = false" /> |
| | | <!-- è®¡åæ§è¡äººåéæ© --> |
| | | <up-action-sheet :show="showExecutorSheet" |
| | | title="éæ©è®¡åæ§è¡äººå" |
| | | :actions="employeeActions" |
| | | @select="onSelectExecutor" |
| | | @close="showExecutorSheet = false" /> |
| | | </view> |
| | | </template> |
| | | |
| | |
| | | import { reactive, ref, computed, onMounted } from "vue"; |
| | | import { onLoad, onReady } from "@dcloudio/uni-app"; |
| | | import FooterButtons from "@/components/FooterButtons.vue"; |
| | | import { |
| | | add, |
| | | update, |
| | | getDeviceLedger, |
| | | } from "@/api/productionManagement/processManagement"; |
| | | import { add, update } from "@/api/productionManagement/processManagement"; |
| | | import { staffOnJobListPage } from "@/api/personnelManagement/onboarding"; |
| | | |
| | | const formRef = ref(null); |
| | | const loading = ref(false); |
| | | const processId = ref(null); |
| | | const pageTitle = computed(() => (processId.value ? "ç¼è¾å·¥åº" : "æ°å¢å·¥åº")); |
| | | |
| | | const processTypeOptions = [ |
| | | "æºå å·¥", |
| | | "宿¿å·è¯å¶ä½", |
| | | "管路ç»å¯¹", |
| | | "ç½ä½è¿æ¥åè°è¯", |
| | | "æµè¯æå", |
| | | "å
¶ä»", |
| | | ]; |
| | | |
| | | const employeeList = ref([]); |
| | | |
| | | const form = ref({ |
| | | no: "", |
| | | name: "", |
| | | processType: "", |
| | | salaryQuota: "", |
| | | planPerson: null, |
| | | executor: null, |
| | | isQuality: false, |
| | | isProduction: false, |
| | | inbound: false, |
| | | reportWork: false, |
| | | remark: "", |
| | | deviceLedgerId: null, |
| | | type: 0, |
| | | }); |
| | | |
| | | const rules = { |
| | | name: [{ required: true, message: "请è¾å
¥å·¥åºåç§°" }], |
| | | name: [ |
| | | { required: true, message: "请è¾å
¥é¨ä»¶" }, |
| | | { max: 100, message: "æå¤100个å符" }, |
| | | ], |
| | | processType: [{ required: true, message: "è¯·éæ©å·¥åºç±»å" }], |
| | | salaryQuota: [ |
| | | { |
| | | validator: (rule, value, callback) => { |
| | | if (value !== "" && value !== null && (isNaN(value) || value < 0)) { |
| | | callback(new Error("å·¥èµå®é¢å¿
é¡»æ¯éè´æ°å")); |
| | | callback(new Error("计åå·¥æ¶å¿
é¡»æ¯éè´æ°å")); |
| | | } else { |
| | | callback(); |
| | | } |
| | |
| | | ], |
| | | }; |
| | | |
| | | const showTypeSheet = ref(false); |
| | | const typeActions = [ |
| | | { name: "计æ¶", value: 0 }, |
| | | { name: "计件", value: 1 }, |
| | | ]; |
| | | const typeText = computed(() => { |
| | | const action = typeActions.find(a => a.value === form.value.type); |
| | | return action ? action.name : ""; |
| | | }); |
| | | const showProcessTypeSheet = ref(false); |
| | | const processTypeActions = processTypeOptions.map(item => ({ name: item, value: item })); |
| | | const processTypeText = ref(""); |
| | | |
| | | const showDeviceSheet = ref(false); |
| | | const deviceActions = ref([]); |
| | | const deviceText = ref(""); |
| | | const showPlanPersonSheet = ref(false); |
| | | const showExecutorSheet = ref(false); |
| | | const planPersonText = ref(""); |
| | | const executorText = ref(""); |
| | | |
| | | const onSelectType = e => { |
| | | form.value.type = e.value; |
| | | showTypeSheet.value = false; |
| | | const employeeActions = computed(() => |
| | | employeeList.value.map(item => ({ |
| | | name: item.staffName, |
| | | id: item.id, |
| | | })) |
| | | ); |
| | | |
| | | const onSelectProcessType = e => { |
| | | form.value.processType = e.value; |
| | | processTypeText.value = e.name; |
| | | showProcessTypeSheet.value = false; |
| | | }; |
| | | |
| | | const onSelectDevice = e => { |
| | | form.value.deviceLedgerId = e.id; |
| | | deviceText.value = e.name; |
| | | showDeviceSheet.value = false; |
| | | const onSelectPlanPerson = e => { |
| | | form.value.planPerson = e.id; |
| | | planPersonText.value = e.name; |
| | | showPlanPersonSheet.value = false; |
| | | }; |
| | | |
| | | const loadDevices = async () => { |
| | | const onSelectExecutor = e => { |
| | | form.value.executor = e.id; |
| | | executorText.value = e.name; |
| | | showExecutorSheet.value = false; |
| | | }; |
| | | |
| | | const loadEmployees = async () => { |
| | | try { |
| | | const { data } = await getDeviceLedger(); |
| | | deviceActions.value = (data || []).map(item => ({ |
| | | name: item.deviceName, |
| | | id: item.id, |
| | | })); |
| | | if (form.value.deviceLedgerId) { |
| | | const device = deviceActions.value.find( |
| | | d => d.id === Number(form.value.deviceLedgerId) |
| | | ); |
| | | if (device) deviceText.value = device.name; |
| | | } |
| | | const res = await staffOnJobListPage({ current: -1, size: -1, staffState: 1 }); |
| | | employeeList.value = res.data?.records || []; |
| | | } catch (error) { |
| | | console.error("å 载设å¤å¤±è´¥", error); |
| | | console.error("å è½½åå·¥å表失败", error); |
| | | } |
| | | }; |
| | | |
| | | const resolveDisplayTexts = () => { |
| | | if (form.value.processType) { |
| | | processTypeText.value = form.value.processType; |
| | | } |
| | | if (form.value.planPerson) { |
| | | const emp = employeeList.value.find(e => e.id === form.value.planPerson); |
| | | if (emp) planPersonText.value = emp.staffName; |
| | | } |
| | | if (form.value.executor) { |
| | | const emp = employeeList.value.find(e => e.id === form.value.executor); |
| | | if (emp) executorText.value = emp.staffName; |
| | | } |
| | | }; |
| | | |
| | |
| | | const item = JSON.parse(decodeURIComponent(option.item)); |
| | | processId.value = item.id; |
| | | Object.assign(form.value, item); |
| | | // å¤çç±»å转æ¢ï¼ç¡®ä¿æ¯æ°å |
| | | form.value.type = Number(form.value.type); |
| | | form.value.isQuality = !!form.value.isQuality; |
| | | form.value.isProduction = !!form.value.isProduction; |
| | | form.value.inbound = !!form.value.inbound; |
| | | form.value.reportWork = !!form.value.reportWork; |
| | | } |
| | | }); |
| | | |
| | |
| | | }); |
| | | |
| | | onMounted(() => { |
| | | loadDevices(); |
| | | loadEmployees().then(() => resolveDisplayTexts()); |
| | | }); |
| | | </script> |
| | | |
| | |
| | | <view class="search-input"> |
| | | <up-input class="search-text" |
| | | v-model="queryParams.name" |
| | | placeholder="请è¾å
¥å·¥åºåç§°" |
| | | placeholder="请è¾å
¥é¨ä»¶åç§°" |
| | | clearable |
| | | @change="handleSearch" /> |
| | | </view> |
| | |
| | | <up-divider></up-divider> |
| | | <view class="item-details"> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">é¨ä»¶ç±»å</text> |
| | | <text class="detail-value">{{ item.processType || "-" }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">å
³è设å¤</text> |
| | | <text class="detail-value">{{ getDeviceName(item.deviceLedgerId) }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">å·¥èµå®é¢</text> |
| | | <text class="detail-value highlight">Â¥{{ item.salaryQuota || 0 }}</text> |
| | | <text class="detail-label">计åå·¥æ¶</text> |
| | | <text class="detail-value highlight">{{ item.salaryQuota || 0 }}å°æ¶</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">å·¥åºç¶æ</text> |
| | | <view class="detail-value"> |
| | | <up-tag :text="item.isQuality ? 'è´¨æ£' : 'éè´¨æ£'" |
| | | :type="item.isQuality ? 'warning' : 'info'" |
| | | size="mini" |
| | | style="margin-left: 8rpx" /> |
| | | <up-tag :text="item.isProduction ? 'ç产' : 'ä¸ç产'" |
| | | :type="item.isProduction ? 'warning' : 'info'" |
| | | size="mini" |
| | | style="margin-left: 8rpx" /> |
| | | <up-tag v-if="item.type !== null && item.type !== undefined" |
| | | :text="item.type == 0 ? '计æ¶' : '计件'" |
| | | :type="item.type == 1 ? 'primary' : 'success'" |
| | | size="mini" |
| | | style="margin-left: 8rpx" /> |
| | | </view> |
| | | <text class="detail-label">计å人å</text> |
| | | <text class="detail-value">{{ getEmployeeName(item.planPerson) }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">è®¡åæ§è¡äººå</text> |
| | | <text class="detail-value">{{ getEmployeeName(item.executor) }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">夿³¨</text> |
| | | <text class="detail-value">{{ item.remark || "-" }}</text> |
| | | </view> |
| | | </view> |
| | | <view class="status-tags"> |
| | | <up-tag :text="item.isQuality ? 'è´¨æ£' : 'éè´¨æ£'" |
| | | :type="item.isQuality ? 'warning' : 'info'" |
| | | size="mini" /> |
| | | <up-tag :text="item.isProduction ? 'ç产' : 'ä¸ç产'" |
| | | :type="item.isProduction ? 'warning' : 'info'" |
| | | size="mini" |
| | | style="margin-left: 8rpx" /> |
| | | </view> |
| | | <view class="action-buttons"> |
| | | <up-button class="action-btn" |
| | |
| | | </view> |
| | | <view v-else |
| | | class="no-data"> |
| | | <text>ææ å·¥åºæ°æ®</text> |
| | | <text>ææ é¨ä»¶æ°æ®</text> |
| | | </view> |
| | | <view class="fab-button" |
| | | @click="goAdd"> |
| | |
| | | del, |
| | | getDeviceLedger, |
| | | } from "@/api/productionManagement/processManagement"; |
| | | import { staffOnJobListPage } from "@/api/personnelManagement/onboarding"; |
| | | |
| | | const queryParams = reactive({ |
| | | name: "", |
| | | }); |
| | | const list = ref([]); |
| | | const deviceOptions = ref([]); |
| | | const employeeOptions = ref([]); |
| | | const pageStatus = ref("loadmore"); |
| | | |
| | | const page = reactive({ |
| | |
| | | return device?.deviceName || "æªå
³è"; |
| | | }; |
| | | |
| | | const getEmployeeName = employeeId => { |
| | | if (!employeeId) return "æªæå®"; |
| | | const emp = employeeOptions.value.find(item => item.id === Number(employeeId)); |
| | | return emp?.staffName || "æªæå®"; |
| | | }; |
| | | |
| | | const loadDevices = async () => { |
| | | try { |
| | | const { data } = await getDeviceLedger(); |
| | | deviceOptions.value = data || []; |
| | | } catch (error) { |
| | | console.error("å 载设å¤å表失败", error); |
| | | } |
| | | }; |
| | | |
| | | const loadEmployees = async () => { |
| | | try { |
| | | const res = await staffOnJobListPage({ current: -1, size: -1, staffState: 1 }); |
| | | employeeOptions.value = res.data?.records || []; |
| | | } catch (error) { |
| | | console.error("å è½½åå·¥å表失败", error); |
| | | } |
| | | }; |
| | | |
| | |
| | | }); |
| | | |
| | | onShow(async () => { |
| | | await loadDevices(); |
| | | await Promise.all([loadDevices(), loadEmployees()]); |
| | | handleSearch(); |
| | | }); |
| | | </script> |
| | |
| | | font-size: 28rpx; |
| | | } |
| | | |
| | | .status-tags { |
| | | display: flex; |
| | | align-items: center; |
| | | padding-bottom: 16rpx; |
| | | } |
| | | |
| | | .action-buttons { |
| | | display: flex; |
| | | justify-content: flex-end; |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <view> |
| | | <!-- ä¸»ç©æå¼¹çª --> |
| | | <view v-if="dialogVisible" class="material-overlay"> |
| | | <view class="material-container"> |
| | | <view class="material-header"> |
| | | <text class="material-title">ç©æ</text> |
| | | <view class="close-btn" @click="dialogVisible = false"> |
| | | <up-icon name="close" size="20" color="#666" /> |
| | | </view> |
| | | </view> |
| | | <scroll-view class="material-body" scroll-y> |
| | | <view v-if="materialTableData.length === 0" class="empty-tip"> |
| | | <text>ææ ç©ææ°æ®</text> |
| | | </view> |
| | | <view v-for="item in materialTableData" :key="item.id" class="material-card"> |
| | | <view class="material-row"> |
| | | <text class="mc-label">å·¥åºåç§°</text> |
| | | <text class="mc-value">{{ item.processName || '-' }}</text> |
| | | </view> |
| | | <view class="material-row"> |
| | | <text class="mc-label">åæåç§°</text> |
| | | <text class="mc-value">{{ item.materialName || '-' }}</text> |
| | | </view> |
| | | <view class="material-row"> |
| | | <text class="mc-label">åæåå·</text> |
| | | <text class="mc-value">{{ item.materialModel || '-' }}</text> |
| | | </view> |
| | | <view class="material-row"> |
| | | <text class="mc-label">计éåä½</text> |
| | | <text class="mc-value">{{ item.unit || '-' }}</text> |
| | | </view> |
| | | <view class="material-row"> |
| | | <text class="mc-label">çº¿è¾¹ä»æ°é</text> |
| | | <text class="mc-value">{{ item.pickQty || 0 }}</text> |
| | | </view> |
| | | <view class="material-row"> |
| | | <text class="mc-label">è¡¥ææ°é</text> |
| | | <text class="mc-value">{{ item.supplementQty || 0 }}</text> |
| | | </view> |
| | | <view class="material-row"> |
| | | <text class="mc-label">å®é
æ°é</text> |
| | | <view class="mc-value"> |
| | | <up-input v-model="item.actualQty" |
| | | type="number" |
| | | placeholder="请è¾å
¥å®é
æ°é" |
| | | clearable |
| | | style="width: 200rpx" /> |
| | | </view> |
| | | </view> |
| | | <view class="material-actions"> |
| | | <up-button size="small" type="primary" @click="openSupplementDialog(item)">è¡¥æ</up-button> |
| | | <up-button size="small" type="info" @click="openSupplementRecordDialog(item)">è¡¥æè®°å½</up-button> |
| | | </view> |
| | | </view> |
| | | </scroll-view> |
| | | <view class="material-footer"> |
| | | <up-button type="primary" :loading="pickSubmitting" @click="handleSubmitPick">é¢ç¨</up-button> |
| | | <up-button @click="dialogVisible = false">åæ¶</up-button> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | |
| | | <!-- è¡¥æå¼¹çª --> |
| | | <view v-if="supplementDialogVisible" class="material-overlay"> |
| | | <view class="material-container supplement-container"> |
| | | <view class="material-header"> |
| | | <text class="material-title">è¡¥æ</text> |
| | | <view class="close-btn" @click="supplementDialogVisible = false"> |
| | | <up-icon name="close" size="20" color="#666" /> |
| | | </view> |
| | | </view> |
| | | <view class="material-body"> |
| | | <up-form :model="supplementForm" ref="supplementFormRef" label-width="140"> |
| | | <up-form-item label="è¡¥ææ°é" prop="supplementQty" required> |
| | | <up-input v-model="supplementForm.supplementQty" |
| | | type="number" |
| | | placeholder="请è¾å
¥è¡¥ææ°é" |
| | | clearable /> |
| | | </up-form-item> |
| | | <up-form-item label="è¡¥æåå " prop="supplementReason" required> |
| | | <up-textarea v-model="supplementForm.supplementReason" |
| | | placeholder="请è¾å
¥è¡¥æåå " |
| | | :maxlength="200" |
| | | autoHeight /> |
| | | </up-form-item> |
| | | </up-form> |
| | | </view> |
| | | <view class="material-footer"> |
| | | <up-button type="primary" :loading="supplementSubmitting" @click="handleSubmitSupplement">ç¡®å®</up-button> |
| | | <up-button @click="supplementDialogVisible = false">åæ¶</up-button> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | |
| | | <!-- è¡¥æè®°å½å¼¹çª --> |
| | | <view v-if="supplementRecordDialogVisible" class="material-overlay"> |
| | | <view class="material-container supplement-record-container"> |
| | | <view class="material-header"> |
| | | <text class="material-title">è¡¥æè®°å½</text> |
| | | <view class="close-btn" @click="supplementRecordDialogVisible = false"> |
| | | <up-icon name="close" size="20" color="#666" /> |
| | | </view> |
| | | </view> |
| | | <scroll-view class="material-body" scroll-y> |
| | | <view v-if="supplementRecordTableData.length === 0" class="empty-tip"> |
| | | <text>ææ è¡¥æè®°å½</text> |
| | | </view> |
| | | <view v-for="item in supplementRecordTableData" :key="item.id" class="record-card"> |
| | | <view class="material-row"> |
| | | <text class="mc-label">è¡¥ææ°é</text> |
| | | <text class="mc-value">{{ item.supplementQty }}</text> |
| | | </view> |
| | | <view class="material-row"> |
| | | <text class="mc-label">è¡¥æåå </text> |
| | | <text class="mc-value">{{ item.supplementReason }}</text> |
| | | </view> |
| | | <view class="material-row"> |
| | | <text class="mc-label">è¡¥æäºº</text> |
| | | <text class="mc-value">{{ item.supplementUserName }}</text> |
| | | </view> |
| | | <view class="material-row"> |
| | | <text class="mc-label">è¡¥ææ¥æ</text> |
| | | <text class="mc-value">{{ item.supplementTime }}</text> |
| | | </view> |
| | | </view> |
| | | </scroll-view> |
| | | <view class="material-footer"> |
| | | <up-button @click="supplementRecordDialogVisible = false">å
³é</up-button> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { computed, nextTick, reactive, ref, watch } from "vue"; |
| | | import { |
| | | listWorkOrderMaterialLedger, |
| | | addWorkOrderMaterialSupplement, |
| | | listWorkOrderMaterialSupplementRecord, |
| | | pickWorkOrderMaterial, |
| | | } from "@/api/productionManagement/workOrder.js"; |
| | | |
| | | const props = defineProps({ |
| | | modelValue: { |
| | | type: Boolean, |
| | | default: false, |
| | | }, |
| | | rowData: { |
| | | type: Object, |
| | | default: () => null, |
| | | }, |
| | | }); |
| | | |
| | | const emit = defineEmits(["update:modelValue", "refresh"]); |
| | | |
| | | const dialogVisible = computed({ |
| | | get: () => props.modelValue, |
| | | set: val => emit("update:modelValue", val), |
| | | }); |
| | | |
| | | const materialTableLoading = ref(false); |
| | | const materialTableData = ref([]); |
| | | const currentMaterialRow = ref(null); |
| | | const currentMaterialOrderRow = ref(null); |
| | | const pickSubmitting = ref(false); |
| | | |
| | | const supplementDialogVisible = ref(false); |
| | | const supplementSubmitting = ref(false); |
| | | const supplementFormRef = ref(null); |
| | | const supplementForm = reactive({ |
| | | supplementQty: null, |
| | | supplementReason: "", |
| | | }); |
| | | |
| | | const supplementRecordDialogVisible = ref(false); |
| | | const supplementRecordLoading = ref(false); |
| | | const supplementRecordTableData = ref([]); |
| | | |
| | | const loadMaterialTable = async row => { |
| | | if (!row?.id) return; |
| | | currentMaterialOrderRow.value = row; |
| | | materialTableLoading.value = true; |
| | | materialTableData.value = []; |
| | | try { |
| | | const res = await listWorkOrderMaterialLedger({ |
| | | workOrderId: row.id, |
| | | processId: row.processId, |
| | | productProcessRouteItemId: row.productProcessRouteItemId, |
| | | }); |
| | | materialTableData.value = res.data || []; |
| | | } catch (e) { |
| | | console.error("è·åç©æå°è´¦å¤±è´¥", e); |
| | | uni.showToast({ title: "è·åç©æå°è´¦å¤±è´¥", icon: "error" }); |
| | | } finally { |
| | | materialTableLoading.value = false; |
| | | } |
| | | }; |
| | | |
| | | watch( |
| | | () => props.modelValue, |
| | | visible => { |
| | | if (visible && props.rowData) { |
| | | loadMaterialTable(props.rowData); |
| | | } |
| | | } |
| | | ); |
| | | |
| | | const resetSupplementForm = () => { |
| | | supplementForm.supplementQty = null; |
| | | supplementForm.supplementReason = ""; |
| | | }; |
| | | |
| | | const openSupplementDialog = row => { |
| | | currentMaterialRow.value = row; |
| | | resetSupplementForm(); |
| | | supplementDialogVisible.value = true; |
| | | }; |
| | | |
| | | const handleSubmitSupplement = () => { |
| | | if (!supplementForm.supplementQty) { |
| | | uni.showToast({ title: "请è¾å
¥è¡¥ææ°é", icon: "none" }); |
| | | return; |
| | | } |
| | | if (!supplementForm.supplementReason) { |
| | | uni.showToast({ title: "请è¾å
¥è¡¥æåå ", icon: "none" }); |
| | | return; |
| | | } |
| | | if (!currentMaterialRow.value?.id) { |
| | | uni.showToast({ title: "缺å°ç©ææç»ID", icon: "none" }); |
| | | return; |
| | | } |
| | | supplementSubmitting.value = true; |
| | | addWorkOrderMaterialSupplement({ |
| | | materialLedgerId: currentMaterialRow.value.id, |
| | | supplementQty: Number(supplementForm.supplementQty), |
| | | supplementReason: supplementForm.supplementReason, |
| | | workOrderId: currentMaterialOrderRow.value?.id, |
| | | }) |
| | | .then(async () => { |
| | | supplementDialogVisible.value = false; |
| | | await loadMaterialTable(currentMaterialOrderRow.value); |
| | | uni.showToast({ title: "è¡¥ææå" }); |
| | | emit("refresh"); |
| | | }) |
| | | .catch(e => { |
| | | console.error("è¡¥æå¤±è´¥", e); |
| | | uni.showToast({ title: "è¡¥æå¤±è´¥", icon: "error" }); |
| | | }) |
| | | .finally(() => { |
| | | supplementSubmitting.value = false; |
| | | }); |
| | | }; |
| | | |
| | | const openSupplementRecordDialog = async row => { |
| | | supplementRecordDialogVisible.value = true; |
| | | supplementRecordLoading.value = true; |
| | | supplementRecordTableData.value = []; |
| | | try { |
| | | const res = await listWorkOrderMaterialSupplementRecord({ |
| | | materialLedgerId: row.id, |
| | | }); |
| | | supplementRecordTableData.value = res.data || []; |
| | | } catch (e) { |
| | | console.error("è·åè¡¥æè®°å½å¤±è´¥", e); |
| | | uni.showToast({ title: "è·åè¡¥æè®°å½å¤±è´¥", icon: "error" }); |
| | | } finally { |
| | | supplementRecordLoading.value = false; |
| | | } |
| | | }; |
| | | |
| | | const validatePickRows = () => { |
| | | if (materialTableData.value.length === 0) { |
| | | return { valid: false, message: "ææ å¯é¢ç¨ç©æ" }; |
| | | } |
| | | const invalidRow = materialTableData.value.find( |
| | | item => |
| | | item.actualQty === null || |
| | | item.actualQty === undefined || |
| | | item.actualQty === "" |
| | | ); |
| | | if (invalidRow) { |
| | | return { valid: false, message: "请填åå®é
æ°éååé¢ç¨" }; |
| | | } |
| | | const exceedRow = materialTableData.value.find(item => { |
| | | const maxQty = Number(item.pickQty || 0) + Number(item.supplementQty || 0); |
| | | return Number(item.actualQty || 0) > maxQty; |
| | | }); |
| | | if (exceedRow) { |
| | | return { valid: false, message: "å®é
æ°éä¸è½å¤§äºé¢ç¨æ°é+è¡¥ææ°é" }; |
| | | } |
| | | return { valid: true, message: "" }; |
| | | }; |
| | | |
| | | const handleSubmitPick = async () => { |
| | | if (!currentMaterialOrderRow.value?.id) return; |
| | | const validateResult = validatePickRows(); |
| | | if (!validateResult.valid) { |
| | | uni.showToast({ title: validateResult.message, icon: "none" }); |
| | | return; |
| | | } |
| | | pickSubmitting.value = true; |
| | | try { |
| | | await pickWorkOrderMaterial({ |
| | | workOrderId: currentMaterialOrderRow.value.id, |
| | | items: materialTableData.value.map(item => ({ |
| | | materialLedgerId: item.id, |
| | | actualQty: Number(item.actualQty || 0), |
| | | })), |
| | | }); |
| | | uni.showToast({ title: "é¢ç¨æå" }); |
| | | await loadMaterialTable(currentMaterialOrderRow.value); |
| | | emit("refresh"); |
| | | } catch (e) { |
| | | console.error("é¢ç¨å¤±è´¥", e); |
| | | uni.showToast({ title: "é¢ç¨å¤±è´¥", icon: "error" }); |
| | | } finally { |
| | | pickSubmitting.value = false; |
| | | } |
| | | }; |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .material-overlay { |
| | | position: fixed; |
| | | top: 0; |
| | | left: 0; |
| | | right: 0; |
| | | bottom: 0; |
| | | background: rgba(0, 0, 0, 0.5); |
| | | z-index: 999; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | |
| | | .material-container { |
| | | width: 92%; |
| | | max-height: 85vh; |
| | | background: #fff; |
| | | border-radius: 16rpx; |
| | | display: flex; |
| | | flex-direction: column; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .supplement-container { |
| | | max-height: 60vh; |
| | | } |
| | | |
| | | .supplement-record-container { |
| | | max-height: 75vh; |
| | | } |
| | | |
| | | .material-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | padding: 24rpx 32rpx; |
| | | border-bottom: 1px solid #f0f0f0; |
| | | flex-shrink: 0; |
| | | } |
| | | |
| | | .material-title { |
| | | font-size: 32rpx; |
| | | font-weight: 600; |
| | | color: #303133; |
| | | } |
| | | |
| | | .close-btn { |
| | | padding: 8rpx; |
| | | } |
| | | |
| | | .material-body { |
| | | flex: 1; |
| | | padding: 16rpx 24rpx; |
| | | overflow-y: auto; |
| | | } |
| | | |
| | | .empty-tip { |
| | | text-align: center; |
| | | padding: 60rpx 0; |
| | | color: #999; |
| | | font-size: 28rpx; |
| | | } |
| | | |
| | | .material-card { |
| | | background: #f9fafb; |
| | | border-radius: 12rpx; |
| | | padding: 20rpx; |
| | | margin-bottom: 16rpx; |
| | | } |
| | | |
| | | .material-row { |
| | | display: flex; |
| | | align-items: center; |
| | | padding: 10rpx 0; |
| | | } |
| | | |
| | | .mc-label { |
| | | width: 160rpx; |
| | | font-size: 26rpx; |
| | | color: #909399; |
| | | flex-shrink: 0; |
| | | } |
| | | |
| | | .mc-value { |
| | | flex: 1; |
| | | font-size: 26rpx; |
| | | color: #303133; |
| | | } |
| | | |
| | | .material-actions { |
| | | display: flex; |
| | | gap: 16rpx; |
| | | margin-top: 16rpx; |
| | | padding-top: 16rpx; |
| | | border-top: 1px solid #ebeef5; |
| | | } |
| | | |
| | | .record-card { |
| | | background: #f9fafb; |
| | | border-radius: 12rpx; |
| | | padding: 20rpx; |
| | | margin-bottom: 16rpx; |
| | | } |
| | | |
| | | .material-footer { |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | gap: 16rpx; |
| | | padding: 20rpx 32rpx; |
| | | border-top: 1px solid #f0f0f0; |
| | | flex-shrink: 0; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <view v-if="visible" class="files-overlay"> |
| | | <view class="files-container"> |
| | | <view class="files-header"> |
| | | <text class="files-title">å·¥åéä»¶</text> |
| | | <view class="close-btn" @click="closeDia"> |
| | | <up-icon name="close" size="20" color="#666" /> |
| | | </view> |
| | | </view> |
| | | <view class="files-toolbar"> |
| | | <up-button type="primary" size="small" @click="handleUpload">ä¸ä¼ å¾ç</up-button> |
| | | <up-button type="error" size="small" plain @click="handleDelete">å é¤</up-button> |
| | | </view> |
| | | <scroll-view class="files-list" scroll-y> |
| | | <view v-if="tableData.length === 0" class="empty-tip"> |
| | | <text>ææ éä»¶</text> |
| | | </view> |
| | | <view v-for="item in tableData" :key="item.id" class="file-item"> |
| | | <view class="file-left" @click="toggleSelect(item)"> |
| | | <up-icon :name="selectedIds.includes(item.id) ? 'checkbox-mark' : 'checkbox'" |
| | | size="20" |
| | | :color="selectedIds.includes(item.id) ? '#2979ff' : '#c0c4cc'" /> |
| | | </view> |
| | | <view class="file-info" @click="handlePreview(item)"> |
| | | <text class="file-name">{{ item.name }}</text> |
| | | </view> |
| | | <view class="file-actions"> |
| | | <text class="action-link" @click="handleDownload(item)">ä¸è½½</text> |
| | | <text class="action-link" @click="handlePreview(item)">é¢è§</text> |
| | | </view> |
| | | </view> |
| | | </scroll-view> |
| | | </view> |
| | | </view> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, getCurrentInstance } from "vue"; |
| | | import { getToken } from "@/utils/auth.js"; |
| | | import { |
| | | productWorkOrderFileAdd, |
| | | productWorkOrderFileDel, |
| | | productWorkOrderFileListPage, |
| | | } from "@/api/productionManagement/productWorkOrderFile.js"; |
| | | |
| | | const { proxy } = getCurrentInstance(); |
| | | |
| | | const visible = ref(false); |
| | | const currentWorkOrderId = ref(""); |
| | | const selectedIds = ref([]); |
| | | const tableData = ref([]); |
| | | const tableLoading = ref(false); |
| | | |
| | | const uploadUrl = import.meta.env.VITE_APP_BASE_API + "/file/upload"; |
| | | |
| | | const openDialog = row => { |
| | | visible.value = true; |
| | | currentWorkOrderId.value = row.id; |
| | | selectedIds.value = []; |
| | | getList(); |
| | | }; |
| | | |
| | | const closeDia = () => { |
| | | visible.value = false; |
| | | }; |
| | | |
| | | const getList = () => { |
| | | tableLoading.value = true; |
| | | productWorkOrderFileListPage({ |
| | | workOrderId: currentWorkOrderId.value, |
| | | current: 1, |
| | | size: 100, |
| | | }) |
| | | .then(res => { |
| | | tableData.value = res.data.records || []; |
| | | }) |
| | | .finally(() => { |
| | | tableLoading.value = false; |
| | | }); |
| | | }; |
| | | |
| | | const toggleSelect = item => { |
| | | const idx = selectedIds.value.indexOf(item.id); |
| | | if (idx > -1) { |
| | | selectedIds.value.splice(idx, 1); |
| | | } else { |
| | | selectedIds.value.push(item.id); |
| | | } |
| | | }; |
| | | |
| | | const handleUpload = () => { |
| | | uni.chooseImage({ |
| | | count: 1, |
| | | sizeType: ["compressed"], |
| | | sourceType: ["album", "camera"], |
| | | success: res => { |
| | | const tempFilePath = res.tempFilePaths[0]; |
| | | uni.showLoading({ title: "ä¸ä¼ ä¸..." }); |
| | | uni.uploadFile({ |
| | | url: uploadUrl, |
| | | filePath: tempFilePath, |
| | | name: "file", |
| | | header: { |
| | | Authorization: "Bearer " + getToken(), |
| | | }, |
| | | success: uploadRes => { |
| | | const data = JSON.parse(uploadRes.data); |
| | | if (data.code === 200) { |
| | | const fileRow = { |
| | | name: data.data.originalName, |
| | | url: data.data.tempPath, |
| | | workOrderId: currentWorkOrderId.value, |
| | | }; |
| | | productWorkOrderFileAdd(fileRow).then(() => { |
| | | uni.showToast({ title: "ä¸ä¼ æå" }); |
| | | getList(); |
| | | }); |
| | | } else { |
| | | uni.showToast({ title: "ä¸ä¼ 失败", icon: "error" }); |
| | | } |
| | | }, |
| | | fail: () => { |
| | | uni.showToast({ title: "ä¸ä¼ 失败", icon: "error" }); |
| | | }, |
| | | complete: () => { |
| | | uni.hideLoading(); |
| | | }, |
| | | }); |
| | | }, |
| | | }); |
| | | }; |
| | | |
| | | const handleDelete = () => { |
| | | if (selectedIds.value.length === 0) { |
| | | uni.showToast({ title: "è¯·éæ©æ°æ®", icon: "none" }); |
| | | return; |
| | | } |
| | | uni.showModal({ |
| | | title: "å é¤", |
| | | content: "éä¸çå
容å°è¢«å é¤ï¼æ¯å¦ç¡®è®¤å é¤ï¼", |
| | | success: res => { |
| | | if (res.confirm) { |
| | | productWorkOrderFileDel(selectedIds.value).then(() => { |
| | | uni.showToast({ title: "å 餿å" }); |
| | | selectedIds.value = []; |
| | | getList(); |
| | | }); |
| | | } |
| | | }, |
| | | }); |
| | | }; |
| | | |
| | | const handleDownload = row => { |
| | | proxy.$download.byUrl(row.url, row.originalFilename); |
| | | }; |
| | | |
| | | const handlePreview = row => { |
| | | uni.previewImage({ |
| | | urls: [row.url], |
| | | current: row.url, |
| | | }); |
| | | }; |
| | | |
| | | defineExpose({ |
| | | openDialog, |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .files-overlay { |
| | | position: fixed; |
| | | top: 0; |
| | | left: 0; |
| | | right: 0; |
| | | bottom: 0; |
| | | background: rgba(0, 0, 0, 0.5); |
| | | z-index: 999; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | |
| | | .files-container { |
| | | width: 90%; |
| | | max-height: 80vh; |
| | | background: #fff; |
| | | border-radius: 16rpx; |
| | | display: flex; |
| | | flex-direction: column; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .files-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | padding: 24rpx 32rpx; |
| | | border-bottom: 1px solid #f0f0f0; |
| | | } |
| | | |
| | | .files-title { |
| | | font-size: 32rpx; |
| | | font-weight: 600; |
| | | color: #303133; |
| | | } |
| | | |
| | | .close-btn { |
| | | padding: 8rpx; |
| | | } |
| | | |
| | | .files-toolbar { |
| | | display: flex; |
| | | justify-content: flex-end; |
| | | gap: 16rpx; |
| | | padding: 16rpx 32rpx; |
| | | border-bottom: 1px solid #f0f0f0; |
| | | } |
| | | |
| | | .files-list { |
| | | flex: 1; |
| | | padding: 16rpx 32rpx; |
| | | overflow-y: auto; |
| | | } |
| | | |
| | | .empty-tip { |
| | | text-align: center; |
| | | padding: 60rpx 0; |
| | | color: #999; |
| | | font-size: 28rpx; |
| | | } |
| | | |
| | | .file-item { |
| | | display: flex; |
| | | align-items: center; |
| | | padding: 20rpx 0; |
| | | border-bottom: 1px solid #f5f5f5; |
| | | } |
| | | |
| | | .file-left { |
| | | margin-right: 16rpx; |
| | | } |
| | | |
| | | .file-info { |
| | | flex: 1; |
| | | } |
| | | |
| | | .file-name { |
| | | font-size: 28rpx; |
| | | color: #303133; |
| | | } |
| | | |
| | | .file-actions { |
| | | display: flex; |
| | | gap: 24rpx; |
| | | flex-shrink: 0; |
| | | } |
| | | |
| | | .action-link { |
| | | font-size: 26rpx; |
| | | color: #2979ff; |
| | | } |
| | | </style> |
| | |
| | | reportWork: "", |
| | | productionOrderRoutingOperationId: "", |
| | | productionOrderId: "", |
| | | productMainId: null, |
| | | productProcessRouteItemId: "", |
| | | workHour: 0, |
| | | type: null, |
| | | paramGroups: {}, |
| | |
| | | |
| | | const submitData = { |
| | | quantity: quantity, |
| | | scrapQty: scrapQty, |
| | | scrapQty: isNaN(scrapQty) ? 0 : scrapQty, |
| | | userId: form.value.userId, |
| | | userName: form.value.userName, |
| | | productionOperationTaskId: form.value.workOrderId, |
| | | productProcessRouteItemId: form.value.productProcessRouteItemId, |
| | | reportWork: form.value.reportWork, |
| | | productMainId: form.value.productMainId, |
| | | productionOrderRoutingOperationId: |
| | | form.value.productionOrderRoutingOperationId, |
| | | productionOrderId: form.value.productionOrderId, |
| | |
| | | Math.max(0, planQuantity - completeQuantity) |
| | | ); |
| | | form.value.workOrderId = orderRow.id || ""; |
| | | form.value.productProcessRouteItemId = |
| | | orderRow.productProcessRouteItemId || ""; |
| | | form.value.reportWork = orderRow.reportWork || ""; |
| | | form.value.productMainId = orderRow.productMainId || null; |
| | | form.value.scrapQty = |
| | | orderRow.scrapQty !== undefined && orderRow.scrapQty !== null |
| | | ? orderRow.scrapQty |
| | | : ""; |
| | | form.value.productionOrderRoutingOperationId = |
| | | orderRow.productionOrderRoutingOperationId || ""; |
| | | form.value.productionOrderId = orderRow.productionOrderId || ""; |
| | |
| | | <template> |
| | | <view class="work-order"> |
| | | <!-- éç¨é¡µé¢å¤´é¨ --> |
| | | <PageHeader title="ç产工å" @back="goBack" /> |
| | | |
| | | |
| | | <!-- æç´¢åºå --> |
| | | <view class="search-section"> |
| | | <view class="search-bar"> |
| | | <view class="search-input"> |
| | | <up-input |
| | | class="search-text" |
| | | placeholder="请è¾å
¥å·¥åç¼å·æç´¢" |
| | | v-model="searchForm.workOrderNo" |
| | | @confirm="handleQuery" |
| | | clearable |
| | | /> |
| | | <up-input class="search-text" |
| | | v-model="data.searchForm.workOrderNo" |
| | | placeholder="å·¥åç¼å·" |
| | | @confirm="handleQuery" |
| | | clearable /> |
| | | </view> |
| | | |
| | | <view class="search-input"> |
| | | <up-input class="search-text" |
| | | v-model="data.searchForm.npsNo" |
| | | placeholder="ç产订åå·" |
| | | @confirm="handleQuery" |
| | | clearable /> |
| | | </view> |
| | | <view class="filter-button" @click="handleQuery"> |
| | | <up-icon name="search" size="24" color="#999"></up-icon> |
| | | </view> |
| | | </view> |
| | | <view class="switch-row"> |
| | | <text class="switch-label">ä»
çæç</text> |
| | | <up-switch v-model="filterMine" @change="handleQuery" size="18" /> |
| | | </view> |
| | | </view> |
| | | |
| | | |
| | | <!-- å·¥åå表 --> |
| | | <scroll-view scroll-y class="ledger-list" v-if="tableData.length > 0" @scrolltolower="loadMore"> |
| | | <view v-for="(item, index) in tableData" :key="item.id || index" class="ledger-item"> |
| | |
| | | <text class="item-tag tag-type">{{ item.workOrderType }}</text> |
| | | </view> |
| | | </view> |
| | | |
| | | |
| | | <up-divider></up-divider> |
| | | |
| | | |
| | | <view class="item-details"> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">ç产订åå·</text> |
| | | <text class="detail-value">{{ item.npsNo || '-' }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">产ååç§°</text> |
| | | <text class="detail-value">{{ item.productName }}</text> |
| | |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">å·¥åºåç§°</text> |
| | | <text class="detail-value">{{ item.processName }}</text> |
| | | <text class="detail-value">{{ item.operationName }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">éæ±/宿æ°é</text> |
| | | <text class="detail-value">{{ item.planQuantity }} / {{ item.completeQuantity }} {{ item.unit }}</text> |
| | | </view> |
| | | |
| | | |
| | | <view class="progress-section"> |
| | | <text class="detail-label">宿è¿åº¦</text> |
| | | <view class="progress-bar"> |
| | | <up-line-progress |
| | | :percentage="toProgressPercentage(item.completionStatus)" |
| | | <up-line-progress |
| | | :percentage="toProgressPercentage(item.completionStatus)" |
| | | activeColor="#2979ff" |
| | | :showText="true" |
| | | ></up-line-progress> |
| | | </view> |
| | | </view> |
| | | |
| | | |
| | | <view class="detail-row"> |
| | | <text class="detail-label">计åå¼å§</text> |
| | | <text class="detail-value">{{ item.planStartTime }}</text> |
| | |
| | | <text class="detail-label">计åç»æ</text> |
| | | <text class="detail-value">{{ item.planEndTime }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">å®é
å¼å§</text> |
| | | <text class="detail-value">{{ item.actualStartTime || '-' }}</text> |
| | | </view> |
| | | <view class="detail-row"> |
| | | <text class="detail-label">å®é
ç»æ</text> |
| | | <text class="detail-value">{{ item.actualEndTime || '-' }}</text> |
| | | </view> |
| | | </view> |
| | | |
| | | <view class="item-actions"> |
| | | <up-button v-if="showStartReport(item)" |
| | | class="action-btn" |
| | | size="small" |
| | | type="primary" |
| | | @click="handleStartWork(item)">å¼å§æ¥å·¥</up-button> |
| | | <up-button v-if="showEndReport(item)" |
| | | class="action-btn" |
| | | size="small" |
| | | type="success" |
| | | @click="goReport(item)">ç»ææ¥å·¥</up-button> |
| | | </view> |
| | | </view> |
| | | <up-loadmore :status="loadStatus" /> |
| | | </scroll-view> |
| | | |
| | | |
| | | <view v-else-if="!loading" class="no-data"> |
| | | <up-empty mode="data" text="ææ å·¥åæ°æ®"></up-empty> |
| | | </view> |
| | | |
| | | <!-- æµè½¬å¡å¼¹çª --> |
| | | <up-popup :show="transferCardVisible" mode="center" @close="transferCardVisible = false" round="10"> |
| | | <view class="qr-popup"> |
| | | <text class="qr-title">å·¥åæµè½¬å¡äºç»´ç </text> |
| | | <view class="qr-box"> |
| | | <geek-qrcode |
| | | v-if="transferCardRowData" |
| | | :val="String(transferCardRowData.id)" |
| | | :size="200" |
| | | /> |
| | | </view> |
| | | <text class="qr-info" v-if="transferCardRowData">{{ transferCardRowData.workOrderNo }}</text> |
| | | <up-button text="å
³é" @click="transferCardVisible = false" style="margin-top: 20px;"></up-button> |
| | | </view> |
| | | </up-popup> |
| | | |
| | | <!-- éä»¶ç»ä»¶ --> |
| | | <FilesDia ref="workOrderFilesRef" /> |
| | | </view> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, toRefs, getCurrentInstance } from "vue"; |
| | | import { ref, reactive } from "vue"; |
| | | import { onShow } from '@dcloudio/uni-app'; |
| | | import { productWorkOrderPage } from "@/api/productionManagement/workOrder.js"; |
| | | import { productWorkOrderPage, startWork } from "@/api/productionManagement/workOrder.js"; |
| | | import PageHeader from "@/components/PageHeader.vue"; |
| | | import FilesDia from "./components/filesDia.vue"; |
| | | import useUserStore from "@/store/modules/user"; |
| | | |
| | | const { proxy } = getCurrentInstance(); |
| | | const userStore = useUserStore(); |
| | | |
| | | const loading = ref(false); |
| | | const tableData = ref([]); |
| | | const loadStatus = ref('loadmore'); |
| | | const transferCardVisible = ref(false); |
| | | const transferCardRowData = ref(null); |
| | | const workOrderFilesRef = ref(null); |
| | | const filterMine = ref(false); |
| | | |
| | | const page = reactive({ |
| | | current: 1, |
| | |
| | | const data = reactive({ |
| | | searchForm: { |
| | | workOrderNo: "", |
| | | npsNo: "", |
| | | }, |
| | | }); |
| | | const { searchForm } = toRefs(data); |
| | | |
| | | const isCompleted = row => { |
| | | const status = Number(row?.completionStatus); |
| | | return Number.isFinite(status) && status >= 100; |
| | | }; |
| | | |
| | | const canOperate = row => !row.endOrder && !isCompleted(row); |
| | | |
| | | const showStartReport = row => canOperate(row) && !row.actualStartTime; |
| | | |
| | | const showEndReport = row => canOperate(row) && !!row.actualStartTime; |
| | | |
| | | const goBack = () => { |
| | | uni.navigateBack(); |
| | |
| | | const getList = () => { |
| | | if (loading.value) return; |
| | | loading.value = true; |
| | | |
| | | const params = { ...searchForm.value, ...page }; |
| | | |
| | | |
| | | const params = { ...data.searchForm, ...page }; |
| | | if (filterMine.value) { |
| | | params.filterMine = true; |
| | | } |
| | | |
| | | productWorkOrderPage(params).then((res) => { |
| | | loading.value = false; |
| | | const records = res.data.records || []; |
| | | tableData.value = page.current === 1 ? records : [...tableData.value, ...records]; |
| | | page.total = res.data.total; |
| | | |
| | | |
| | | if (tableData.value.length >= page.total) { |
| | | loadStatus.value = 'nomore'; |
| | | } else { |
| | |
| | | return Math.round(n); |
| | | }; |
| | | |
| | | const showTransferCard = (row) => { |
| | | transferCardRowData.value = row; |
| | | transferCardVisible.value = true; |
| | | const handleStartWork = (row) => { |
| | | uni.showModal({ |
| | | title: "æç¤º", |
| | | content: "确认å¼å§æ¥å·¥ï¼", |
| | | success: res => { |
| | | if (res.confirm) { |
| | | startWork({ |
| | | productionOperationTaskId: row.id, |
| | | userId: userStore.id, |
| | | }) |
| | | .then(() => { |
| | | uni.showToast({ title: "å¼å§æ¥å·¥æå" }); |
| | | handleQuery(); |
| | | }) |
| | | .catch(() => { |
| | | uni.showToast({ title: "å¼å§æ¥å·¥å¤±è´¥", icon: "error" }); |
| | | }); |
| | | } |
| | | }, |
| | | }); |
| | | }; |
| | | |
| | | const openWorkOrderFiles = (row) => { |
| | | workOrderFilesRef.value?.openDialog(row); |
| | | const goReport = (row) => { |
| | | uni.navigateTo({ |
| | | url: `/pages/productionManagement/productionReport/index?orderRow=${encodeURIComponent( |
| | | JSON.stringify(row) |
| | | )}`, |
| | | }); |
| | | }; |
| | | |
| | | onShow(() => { |
| | |
| | | gap: 10px; |
| | | padding: 12px 0; |
| | | border-top: 1px solid #f5f5f5; |
| | | |
| | | :deep(.up-button) { |
| | | |
| | | .action-btn { |
| | | margin: 0; |
| | | width: auto; |
| | | } |
| | | } |
| | | |
| | | .qr-popup { |
| | | padding: 30px; |
| | | background-color: #fff; |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | |
| | | .qr-title { |
| | | font-size: 18px; |
| | | font-weight: bold; |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .qr-box { |
| | | padding: 20px; |
| | | background-color: #fff; |
| | | box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1); |
| | | border-radius: 8px; |
| | | margin-bottom: 15px; |
| | | } |
| | | |
| | | .qr-info { |
| | | font-size: 14px; |
| | | color: #666; |
| | | } |
| | | } |
| | | |
| | |
| | | <script setup> |
| | | import { ref, onMounted, nextTick, reactive, computed } from "vue"; |
| | | import { userLoginFacotryList } from "@/api/login"; |
| | | import { getProductWorkOrderById } from "@/api/productionManagement/productionReporting"; |
| | | import DownloadProgressMask from "@/components/DownloadProgressMask.vue"; |
| | | import modal from "@/plugins/modal"; |
| | | import useUserStore from "@/store/modules/user"; |
| | |
| | | icon: "/static/images/icon/gongxuguanli.svg", |
| | | label: "å·¥åºç®¡ç", |
| | | }, |
| | | { |
| | | icon: "/static/images/icon/bom.svg", |
| | | label: "BOM", |
| | | }, |
| | | // { |
| | | // icon: "/static/images/icon/bom.svg", |
| | | // label: "BOM", |
| | | // }, |
| | | { |
| | | icon: "/static/images/icon/gongyiluxian.svg", |
| | | label: "å·¥èºè·¯çº¿", |
| | |
| | | icon: "/static/images/icon/kehudangan.svg", |
| | | label: "å®¢æ·æ¡£æ¡", |
| | | }, |
| | | { |
| | | icon: "/static/images/icon/xiaoshoubaojia.svg", |
| | | label: "é宿¥ä»·", |
| | | }, |
| | | // { |
| | | // icon: "/static/images/icon/xiaoshoubaojia.svg", |
| | | // label: "é宿¥ä»·", |
| | | // }, |
| | | { |
| | | icon: "/static/images/icon/xiaoshoutaizhang.svg", |
| | | label: "éå®å°è´¦", |
| | |
| | | }); |
| | | break; |
| | | case "ç产æ¥å·¥": |
| | | getcode(); |
| | | uni.navigateTo({ |
| | | url: "/pages/productionManagement/workOrder/index", |
| | | }); |
| | | break; |
| | | case "æ¥å·¥å°è´¦": |
| | | uni.navigateTo({ |
| | |
| | | factoryList.value = []; |
| | | }); |
| | | } |
| | | const getcode = async () => { |
| | | uni.scanCode({ |
| | | success: async res => { |
| | | // è§£æäºç»´ç å
容 |
| | | const scanResult = res.result; |
| | | let orderRow = ""; |
| | | |
| | | // å¤ææ«æç»ææ¯å¦ä¸ºçº¯æ°åï¼idï¼ |
| | | const isNumericId = /^\d+$/.test(scanResult.trim()); |
| | | |
| | | if (isNumericId) { |
| | | // 妿æ¯çº¯æ°åï¼æ ¹æ® id è·åå·¥åæ°æ® |
| | | const workOrderId = scanResult.trim(); |
| | | modal.loading("æ£å¨è·åå·¥åä¿¡æ¯..."); |
| | | try { |
| | | const workRes = await getProductWorkOrderById({ id: workOrderId }); |
| | | modal.closeLoading(); |
| | | |
| | | console.log("å·¥åæ¥è¯¢ç»æ:", workRes); |
| | | |
| | | if (workRes.code === 200 && workRes.data) { |
| | | // æ°æ¥å£è¿åçæ¯å个对象ï¼ä¸æ¯æ°ç» |
| | | const workData = workRes.data; |
| | | console.log("工忰æ®:", workData); |
| | | |
| | | if (workData.endOrder === true) { |
| | | modal.msgError("该订åå·²ç»æï¼æ æ³æ¥å·¥"); |
| | | return; |
| | | } |
| | | |
| | | orderRow = JSON.stringify(workData); |
| | | |
| | | console.log("æé çorderRow:", orderRow); |
| | | } else { |
| | | modal.msgError("æªæ¾å°å¯¹åºçå·¥åä¿¡æ¯"); |
| | | return; |
| | | } |
| | | } catch (error) { |
| | | modal.closeLoading(); |
| | | console.error("è·åå·¥åä¿¡æ¯å¤±è´¥:", error); |
| | | modal.msgError("è·åå·¥åä¿¡æ¯å¤±è´¥: " + (error.message || "æªç¥é误")); |
| | | return; |
| | | } |
| | | } else { |
| | | // 妿䏿¯çº¯æ°åï¼å°è¯ä»æ«ç ç»æä¸æåorderRowåæ° |
| | | try { |
| | | // å¤çæ··åæ ¼å¼: http://...?orderRow={...} |
| | | const orderRowStart = scanResult.indexOf("orderRow={"); |
| | | if (orderRowStart !== -1) { |
| | | // æåä»orderRow={å¼å§çJSONå
容 |
| | | const jsonPart = scanResult.substring(orderRowStart + 9); // 9æ¯"orderRow=".length |
| | | orderRow = jsonPart; |
| | | } else { |
| | | // å¦æç´æ¥æ¯JSONå符串ï¼å°è¯è§£æ |
| | | orderRow = scanResult; |
| | | } |
| | | } catch (e) { |
| | | console.error(e, "è§£æå¤±è´¥====????====="); |
| | | orderRow = ""; |
| | | } |
| | | |
| | | // éªè¯æ¯å¦ä¸ºææçJSON |
| | | try { |
| | | JSON.parse(orderRow); |
| | | } catch (error) { |
| | | modal.msgError("订åè§£æå¤±è´¥ï¼è¯·æ£æ¥äºç»´ç æ ¼å¼"); |
| | | return; |
| | | } |
| | | } |
| | | // æ«ç æåå跳转å°ç产æ¥å·¥é¡µé¢ï¼å¹¶ä¼ éorderRowåæ° |
| | | uni.navigateTo({ |
| | | url: `/pages/productionManagement/productionReport/index?orderRow=${orderRow}`, |
| | | }); |
| | | }, |
| | | fail: err => { |
| | | uni.showToast({ |
| | | title: "æ«ç 失败", |
| | | icon: "none", |
| | | }); |
| | | }, |
| | | }); |
| | | }; |
| | | const changeFactory = async arr => { |
| | | show.value = false; |
| | | const factoryId = factoryListTem.value[arr.indexs[0]].deptId; |