From 47e1cecb6f5cd01c029ff1a2f1658326a33025f4 Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期六, 15 十一月 2025 11:51:35 +0800
Subject: [PATCH] 生产管控-左右页面逻辑修改
---
src/pages/index.vue | 6
src/pages.json | 21
src/utils/request.ts | 22
src/pages/productionManagement/productionDispatching/components/autoDispatchDia.vue | 381 ++++++++++
src/api/productionManagement/productionOrder.js | 34
src/pages/productionManagement/operationScheduling/components/formDia.vue | 282 +++++++
src/pages/sales/salesAccount/index.vue | 5
src/pages/productionManagement/operationScheduling/index.vue | 282 +++++++
src/pages/login.vue | 2
src/pages/sales/salesAccount/view.vue | 4
src/pages/sales/salesAccount/detail.vue | 19
src/pages/productionManagement/productionDispatching/index.vue | 580 +++++++++------
src/pages/productionManagement/productionReporting/components/formDia.vue | 160 ++++
src/config.js | 5
src/pages/productionManagement/productionReporting/index.vue | 202 +++++
src/pages/productionManagement/productionCosting/index.vue | 107 ++
16 files changed, 1,857 insertions(+), 255 deletions(-)
diff --git a/src/api/productionManagement/productionOrder.js b/src/api/productionManagement/productionOrder.js
index 9c1e7de..956b6e7 100644
--- a/src/api/productionManagement/productionOrder.js
+++ b/src/api/productionManagement/productionOrder.js
@@ -17,6 +17,14 @@
data: query,
});
}
+// 鑷姩娲惧伐
+export function productionDispatchList(query) {
+ return request({
+ url: "/salesLedger/scheduling/productionDispatchList",
+ method: "post",
+ data: query,
+ });
+}
// 鑾峰彇鐐掓満姝e湪宸ヤ綔閲忔暟鎹�
export function schedulingList(query) {
return request({
@@ -42,4 +50,30 @@
method: "post",
data: data,
});
+}
+
+// 鏌ヨ鎹熻�楃巼
+export function getLossRate() {
+ return request({
+ url: "/salesLedger/scheduling/loss",
+ method: "get",
+ });
+}
+
+// 鏂板鎹熻�楃巼
+export function addLossRate(data) {
+ return request({
+ url: "/salesLedger/scheduling/addLoss",
+ method: "post",
+ data: data,
+ });
+}
+
+// 淇敼鎹熻�楃巼
+export function updateLossRate(data) {
+ return request({
+ url: "/salesLedger/scheduling/updateLoss",
+ method: "post",
+ data: data,
+ });
}
\ No newline at end of file
diff --git a/src/config.js b/src/config.js
index ee29e3c..e451dd8 100644
--- a/src/config.js
+++ b/src/config.js
@@ -2,9 +2,8 @@
const config = {
// baseUrl: 'https://vue.ruoyi.vip/prod-api',
// baseUrl: 'http://localhost/prod-api',
- // baseUrl: 'http://114.132.189.42:9066', // 瀹佸娑︽嘲
- // baseUrl: 'http://114.132.189.42:9068', // 鏂扮枂娴峰窛寮�蹇�
- baseUrl: 'http://192.168.1.147:8080', // 鏈湴娴嬭瘯
+ baseUrl: 'http://114.132.189.42:9068', // 鏂扮枂娴峰窛寮�蹇�
+ // baseUrl: 'http://192.168.1.185:9988', // 鏈湴娴嬭瘯
//cloud鍚庡彴缃戝叧鍦板潃
// baseUrl: 'http://192.168.10.3:8080',
// 搴旂敤淇℃伅
diff --git a/src/pages.json b/src/pages.json
index 4151447..8aca2bc 100644
--- a/src/pages.json
+++ b/src/pages.json
@@ -413,6 +413,27 @@
"navigationBarTitleText": "鐢熶骇娲惧伐",
"navigationStyle": "custom"
}
+ },
+ {
+ "path": "pages/productionManagement/operationScheduling/index",
+ "style": {
+ "navigationBarTitleText": "宸ュ簭鎺掍骇",
+ "navigationStyle": "custom"
+ }
+ },
+ {
+ "path": "pages/productionManagement/productionReporting/index",
+ "style": {
+ "navigationBarTitleText": "鐢熶骇鎶ュ伐",
+ "navigationStyle": "custom"
+ }
+ },
+ {
+ "path": "pages/productionManagement/productionCosting/index",
+ "style": {
+ "navigationBarTitleText": "鐢熶骇鏍哥畻",
+ "navigationStyle": "custom"
+ }
}
],
"subPackages": [
diff --git a/src/pages/index.vue b/src/pages/index.vue
index 7a0673c..7922cc1 100644
--- a/src/pages/index.vue
+++ b/src/pages/index.vue
@@ -437,17 +437,17 @@
break;
case '宸ュ簭鎺掍骇':
uni.navigateTo({
- url: '/pages/productionManagement/processScheduling/index'
+ url: '/pages/productionManagement/operationScheduling/index'
});
break;
case '鐢熶骇鎶ュ伐':
uni.navigateTo({
- url: '/pages/productionManagement/productionReport/index'
+ url: '/pages/productionManagement/productionReporting/index'
});
break;
case '鐢熶骇鏍哥畻':
uni.navigateTo({
- url: '/pages/productionManagement/productionAccounting/index'
+ url: '/pages/productionManagement/productionCosting/index'
});
break;
case '璁惧鍙拌处':
diff --git a/src/pages/login.vue b/src/pages/login.vue
index 860fd09..1b997d8 100644
--- a/src/pages/login.vue
+++ b/src/pages/login.vue
@@ -138,8 +138,6 @@
showToast("璇疯緭鍏ユ偍鐨勮处鍙�")
} else if (loginForm.value.password === "") {
showToast("璇疯緭鍏ユ偍鐨勫瘑鐮�")
- } else if (loginForm.value.factoryId === "") {
- showToast("璇烽�夋嫨鍏徃")
} else {
showToast("鐧诲綍涓紝璇疯�愬績绛夊緟...")
pwdLogin()
diff --git a/src/pages/productionManagement/operationScheduling/components/formDia.vue b/src/pages/productionManagement/operationScheduling/components/formDia.vue
new file mode 100644
index 0000000..c5b0599
--- /dev/null
+++ b/src/pages/productionManagement/operationScheduling/components/formDia.vue
@@ -0,0 +1,282 @@
+<template>
+ <view>
+ <up-popup v-model:show="dialogFormVisible" mode="bottom" round="12" @close="closeDia" :customStyle="{ height: '85vh' }">
+ <view class="dia-container">
+ <view class="dia-header">
+ <text class="title">宸ュ簭鎺掍骇</text>
+ <up-button size="mini" @click="addRow" type="primary">鏂板</up-button>
+ <text class="pending">寰呮帓浜ф暟閲忥細{{ pendingNum }}</text>
+ </view>
+
+ <scroll-view class="rows" scroll-y>
+ <view v-for="(row, index) in tableData" :key="index" class="row-card">
+ <view class="row-header">
+ <text class="row-index">#{{ index + 1 }}</text>
+ <up-button size="mini" type="error" plain @click="removeRow(index)">鍒犻櫎</up-button>
+ </view>
+
+ <up-form>
+ <up-form-item label="宸ュ簭" label-width="80">
+ <up-input v-model="row.process" placeholder="璇疯緭鍏ュ伐搴�" />
+ </up-form-item>
+ <up-form-item label="鍗曚綅" label-width="80">
+ <up-input v-model="row.unit" placeholder="璇疯緭鍏ュ崟浣�" />
+ </up-form-item>
+ <up-form-item label="鍙e懗/鍝佸悕/瑙勬牸" label-width="110">
+ <up-input v-model="row.type" placeholder="璇疯緭鍏�" />
+ </up-form-item>
+ <up-form-item label="鎺掍骇鏁伴噺" label-width="80">
+ <up-input v-model.number="row.schedulingNum" type="number" placeholder="璇疯緭鍏�" />
+ </up-form-item>
+ <up-form-item label="宸ユ椂瀹氶" label-width="80">
+ <up-input v-model.number="row.workHours" type="number" placeholder="璇疯緭鍏�" />
+ </up-form-item>
+ <up-form-item label="鎺掍骇鏃ユ湡" label-width="80" @click="openDatePicker(index)">
+ <up-input v-model="row.schedulingDate" placeholder="閫夋嫨鏃ユ湡" readonly @click="openDatePicker(index)" />
+ <template #right>
+ <up-icon name="calendar" @click="openDatePicker(index)"></up-icon>
+ </template>
+ </up-form-item>
+ <up-form-item label="鎺掍骇浜�" label-width="80" @click="openUserPicker(index)">
+ <up-input v-model="row.schedulingUserName" placeholder="閫夋嫨浜哄憳" readonly @click="openUserPicker(index)" />
+ <template #right>
+ <up-icon name="arrow-right" @click="openUserPicker(index)"></up-icon>
+ </template>
+ </up-form-item>
+ <up-form-item label="澶囨敞" label-width="80">
+ <up-input v-model="row.remark" placeholder="璇疯緭鍏ュ娉�" />
+ </up-form-item>
+ </up-form>
+ </view>
+ </scroll-view>
+
+ <view class="summary">
+ <text>鎺掍骇鏁伴噺鍚堣锛歿{ totalSchedulingNum }}</text>
+ </view>
+
+ <view class="dia-footer">
+ <up-button type="primary" @click="submitForm">纭</up-button>
+ <up-button @click="closeDia">鍙栨秷</up-button>
+ </view>
+ </view>
+ </up-popup>
+
+ <!-- 鏃ユ湡閫夋嫨鍣紙up 绯诲垪锛� -->
+ <up-popup :show="datePicker.show" mode="bottom" @close="datePicker.show = false">
+ <up-datetime-picker :show="true" v-model="datePicker.valueData" mode="date" @confirm="onDateConfirm" @cancel="datePicker.show = false" />
+ </up-popup>
+
+ <!-- 浜哄憳閫夋嫨鍣紙up 绯诲垪 action-sheet锛� -->
+ <up-action-sheet
+ :show="userPicker.show"
+ :actions="userActionList"
+ title="閫夋嫨浜哄憳"
+ @select="onUserSelect"
+ @close="userPicker.show = false"
+ />
+ </view>
+</template>
+
+<script setup>
+import { ref, getCurrentInstance, computed } from 'vue'
+import { userListNoPageByTenantId } from '@/api/system/user.js'
+import { processScheduling } from '@/api/productionManagement/operationScheduling.js'
+const { proxy } = getCurrentInstance()
+const emit = defineEmits(['close'])
+
+const dialogFormVisible = ref(false)
+const operationType = ref('')
+const tableData = ref([])
+const unitFromRow = ref('')
+const idFromRow = ref('')
+const specificationModelFromRow = ref('')
+const pendingNum = ref(0)
+const userList = ref([])
+const receive = ref('')
+
+// pickers
+const datePicker = ref({ show: false, valueData: Date.now(), rowIndex: -1 })
+
+const userPicker = ref({ show: false, rowIndex: -1 })
+
+// ActionSheet 鏁版嵁
+const userActionList = computed(() => {
+ return userList.value.map(u => ({ name: u.nickName, value: u.userId }))
+})
+
+const totalSchedulingNum = computed(() => {
+ return tableData.value.reduce((sum, r) => sum + (Number(r.schedulingNum || 0)), 0)
+})
+
+const userLabel = (uid) => {
+ const u = userList.value.find(u => u.userId === uid)
+ return u ? u.nickName : ''
+}
+
+// 鎵撳紑寮规
+const openDialog = (type, row) => {
+ operationType.value = type
+ dialogFormVisible.value = true
+ userListNoPageByTenantId().then((res) => {
+ userList.value = res.data
+ })
+
+ pendingNum.value = row?.pendingNum ?? 0
+ unitFromRow.value = row?.unit ?? ''
+ idFromRow.value = row?.id ?? ''
+ specificationModelFromRow.value = row?.specificationModel ?? ''
+ tableData.value = [createRow()]
+}
+
+const createRow = () => ({
+ id: idFromRow.value,
+ process: '',
+ schedulingDate: '',
+ schedulingNum: null,
+ schedulingUserId: '',
+ schedulingUserName: '',
+ workHours: null,
+ unit: unitFromRow.value,
+ remark: '',
+ type: specificationModelFromRow.value,
+})
+
+const openDatePicker = (idx) => {
+ datePicker.value.rowIndex = idx
+ datePicker.value.valueData = Date.now()
+ datePicker.value.show = true
+}
+const onDateConfirm = (e) => {
+ const val = e.value
+ const d = new Date(val)
+ const y = d.getFullYear()
+ const m = String(d.getMonth() + 1).padStart(2, '0')
+ const day = String(d.getDate()).padStart(2, '0')
+ const str = `${y}-${m}-${day}`
+ if (datePicker.value.rowIndex > -1) {
+ tableData.value[datePicker.value.rowIndex].schedulingDate = str
+ }
+ datePicker.value.show = false
+}
+
+const openUserPicker = (idx) => {
+ userPicker.value.rowIndex = idx
+ userPicker.value.show = true
+}
+const onUserSelect = (item) => {
+ if (item && userPicker.value.rowIndex > -1) {
+ const row = tableData.value[userPicker.value.rowIndex]
+ row.schedulingUserId = item.value
+ row.schedulingUserName = item.name
+ }
+ userPicker.value.show = false
+}
+
+const submitForm = () => {
+ // 1. 妫�鏌ユ瘡涓�琛屾槸鍚﹀~鍐欏畬鏁�
+ for (let i = 0; i < tableData.value.length; i++) {
+ const row = tableData.value[i]
+ if (!row.process || !row.schedulingDate || row.schedulingNum === '' || row.schedulingNum === null || !row.schedulingUserId || row.workHours === '' || row.workHours === null || !row.unit) {
+ uni.showToast({ title: `绗�${i + 1}琛屾暟鎹湭濉啓瀹屾暣`, icon: 'none' })
+ return
+ }
+ }
+ // 2. 鍚堣鎺掍骇鏁伴噺
+ const total = tableData.value.reduce((sum, row) => sum + Number(row.schedulingNum || 0), 0)
+ if (total > Number(pendingNum.value)) {
+ uni.showToast({ title: '鎺掍骇鏁伴噺鍚堣涓嶈兘瓒呰繃寰呮帓浜ф暟閲�', icon: 'none' })
+ return
+ }
+ // 3. 鎷艰鏁版嵁
+ const submitData = tableData.value.map(row => {
+ const { loss, ...rest } = row
+ return { ...rest, receive: receive.value }
+ })
+ processScheduling(submitData).then(() => {
+ uni.showToast({ title: '鎻愪氦鎴愬姛', icon: 'success' })
+ closeDia()
+ })
+}
+
+// 鍏抽棴寮规
+const closeDia = () => {
+ dialogFormVisible.value = false
+ receive.value = ''
+ tableData.value = []
+ unitFromRow.value = ''
+ idFromRow.value = ''
+ specificationModelFromRow.value = ''
+ pendingNum.value = 0
+ emit('close')
+}
+defineExpose({ openDialog })
+
+const addRow = () => {
+ tableData.value.push(createRow())
+}
+const removeRow = (index) => {
+ tableData.value.splice(index, 1)
+}
+</script>
+
+<style scoped lang="scss">
+.dia-container {
+ padding: 12px;
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+}
+.dia-header {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ margin-bottom: 10px;
+}
+.dia-header .title {
+ font-weight: 600;
+ font-size: 16px;
+ color: #333;
+}
+.dia-header .pending {
+ margin-left: auto;
+ color: #666;
+ font-size: 12px;
+}
+.rows {
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+ overflow-y: auto;
+ -webkit-overflow-scrolling: touch;
+ flex: 1;
+ min-height: 0;
+}
+.row-card {
+ background: #fff;
+ border-radius: 10px;
+ padding: 8px;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.05);
+}
+.row-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 6px;
+}
+.row-index {
+ color: #999;
+ font-size: 12px;
+}
+.summary {
+ padding: 10px 0;
+ color: #333;
+ font-size: 14px;
+ text-align: right;
+}
+.dia-footer {
+ display: flex;
+ gap: 10px;
+ justify-content: flex-end;
+ padding-top: 8px;
+}
+</style>
\ No newline at end of file
diff --git a/src/pages/productionManagement/operationScheduling/index.vue b/src/pages/productionManagement/operationScheduling/index.vue
new file mode 100644
index 0000000..a3d86a5
--- /dev/null
+++ b/src/pages/productionManagement/operationScheduling/index.vue
@@ -0,0 +1,282 @@
+<template>
+ <view class="op-scheduling">
+ <PageHeader title="宸ュ簭鎺掍骇" />
+
+ <view class="search_form">
+ <u-form>
+ <view class="form-row">
+ <u-form-item label="瀹㈡埛鍚嶇О" label-width="80">
+ <up-input v-model="searchForm.customerName" placeholder="璇疯緭鍏�" clearable @change="handleQuery" />
+ </u-form-item>
+ <u-form-item label="椤圭洰鍚嶇О" label-width="80">
+ <up-input v-model="searchForm.projectName" placeholder="璇疯緭鍏�" clearable @change="handleQuery" />
+ </u-form-item>
+ </view>
+ <view class="form-row">
+ <u-form-item label="鐘舵��" label-width="80">
+ <up-input v-model="statusDisplay" placeholder="璇烽�夋嫨鐘舵��" readonly @click="showStatusPicker = true" />
+ </u-form-item>
+ </view>
+ <view class="form-actions">
+ <u-button type="primary" @click="handleQuery" size="small">鎼滅储</u-button>
+ </view>
+ </u-form>
+ </view>
+
+ <!-- 椤堕儴鎿嶄綔宸茬Щ闄� -->
+
+ <view class="list_container">
+ <u-loading-icon v-if="tableLoading" text="鍔犺浇涓�..."></u-loading-icon>
+ <view v-else>
+ <view v-if="!tableData || tableData.length === 0" class="empty">鏆傛棤鏁版嵁</view>
+ <view v-else class="card_list">
+ <view v-for="item in tableData" :key="item.id" class="card_item">
+ <view class="card_header">
+ <u-tag :type="statusType(item.status)" size="mini">{{ statusText(item.status) }}</u-tag>
+ <text class="card_title">{{ item.projectName }}</text>
+ </view>
+ <view class="card_body">
+ <view class="row"><text class="label">娲惧伐鏃ユ湡</text><text class="value">{{ item.schedulingDate }}</text></view>
+ <view class="row"><text class="label">娲惧伐浜�</text><text class="value">{{ item.schedulingUserName }}</text></view>
+ <view class="row"><text class="label">鍚堝悓鍙�</text><text class="value">{{ item.salesContractNo }}</text></view>
+ <view class="row"><text class="label">瀹㈡埛鍚堝悓鍙�</text><text class="value">{{ item.customerContractNo }}</text></view>
+ <view class="row"><text class="label">瀹㈡埛鍚嶇О</text><text class="value">{{ item.customerName }}</text></view>
+ <view class="row"><text class="label">浜у搧澶х被</text><text class="value">{{ item.productCategory }}</text></view>
+ <view class="row"><text class="label">瑙勬牸鍨嬪彿</text><text class="value">{{ item.specificationModel }}</text></view>
+ <view class="row"><text class="label">缁戝畾鏈哄櫒</text><text class="value">{{ item.speculativeTradingName }}</text></view>
+ <view class="row inline">
+ <view class="col"><text class="label">鍗曚綅</text><text class="value">{{ item.unit }}</text></view>
+ <view class="col"><text class="label">鎺掍骇鎬绘暟</text><text class="value">{{ item.schedulingNum }}</text></view>
+ <view class="col"><text class="label">宸叉帓浜ф暟閲�</text><text class="value">{{ item.successNum }}</text></view>
+ <view class="col"><text class="label">寰呮帓浜ф暟閲�</text><text class="value">{{ item.pendingNum }}</text></view>
+ </view>
+ </view>
+ <view class="card_actions">
+ <u-button
+ type="primary"
+ size="small"
+ @click="openForm('add', item)"
+ :disabled="item.pendingNum == 0"
+ >宸ュ簭鎺掍骇</u-button>
+ <u-button
+ type="error"
+ plain
+ size="small"
+ class="ml8"
+ @click="handleCancel(item)"
+ :disabled="item.status == 3"
+ >鍙栨秷鎺掍骇</u-button>
+ </view>
+ </view>
+ </view>
+
+
+ </view>
+ </view>
+
+ <form-dia ref="formDia" @close="handleQuery"></form-dia>
+
+ <!-- 鐘舵�侀�夋嫨鍣紙up 绯诲垪锛� -->
+ <up-action-sheet
+ :show="showStatusPicker"
+ :actions="statusActions"
+ title="閫夋嫨鐘舵��"
+ @select="onStatusSelect"
+ @close="showStatusPicker = false"
+ />
+ </view>
+</template>
+
+<script setup>
+import { onMounted, ref, reactive, toRefs, nextTick, getCurrentInstance, computed } from 'vue'
+import PageHeader from '@/components/PageHeader.vue'
+import FormDia from './components/formDia.vue'
+import dayjs from 'dayjs'
+import { listPageProcess, productionDispatchDelete } from '@/api/productionManagement/operationScheduling.js'
+
+const data = reactive({
+ searchForm: {
+ staffName: "",
+ status: 1,
+ entryDate: null, // 褰曞叆鏃ユ湡
+ entryDateStart: undefined,
+ entryDateEnd: undefined,
+ },
+});
+const { searchForm } = toRefs(data);
+const tableData = ref([])
+const tableLoading = ref(false)
+const page = reactive({
+ current: -1,
+ size: -1,
+});
+const formDia = ref()
+const { proxy } = getCurrentInstance()
+
+// 鐘舵�侀�夋嫨鍣�
+const showStatusPicker = ref(false)
+const statusOptions = ref([
+ { label: '寰呮帓浜�', value: 1 },
+ { label: '鎺掍骇涓�', value: 2 },
+ { label: '宸叉帓浜�', value: 3 }
+])
+const statusActions = computed(() => statusOptions.value.map(o => ({ name: o.label, value: o.value })))
+const statusDisplay = ref('')
+
+// 鏃ユ湡鑼冨洿绛涢�夊凡绉婚櫎
+
+// picker handlers
+const onStatusSelect = (item) => {
+ if (item) {
+ searchForm.value.status = item.value
+ statusDisplay.value = item.name
+ handleQuery()
+ }
+ showStatusPicker.value = false
+}
+// 鏃ユ湡鑼冨洿鍥炶皟宸茬Щ闄�
+
+// status display helpers
+const statusText = (s) => {
+ if (s == 3) return '宸叉帓浜�'
+ if (s == 1) return '寰呮帓浜�'
+ return '鎺掍骇涓�'
+}
+const statusType = (s) => {
+ if (s == 3) return 'success'
+ if (s == 1) return 'primary'
+ return 'warning'
+}
+
+// 鏌ヨ鍒楄〃
+/** 鎼滅储鎸夐挳鎿嶄綔 */
+const handleQuery = () => {
+ page.current = -1;
+ page.size = -1;
+ getList();
+};
+// changeDaterange 宸茬Щ闄�
+const getList = () => {
+ tableLoading.value = true;
+ const params = { ...searchForm.value, ...page };
+ params.entryDate = undefined
+ listPageProcess(params).then(res => {
+ tableLoading.value = false;
+ tableData.value = res.data.records.map(item => ({
+ ...item,
+ pendingNum: (Number(item.schedulingNum) || 0) - (Number(item.successNum) || 0)
+ }));
+ }).catch(err => {
+ tableLoading.value = false;
+ })
+};
+// 鍙栨秷澶氶�夌浉鍏抽�昏緫锛岄噰鐢ㄥ崱鐗囧唴鍗曟潯鎿嶄綔
+
+// 鎵撳紑寮规
+const openForm = (type, row) => {
+ if (!row) {
+ uni.showToast({ title: '鏈壘鍒版暟鎹�', icon: 'none' })
+ return;
+ }
+ if ((Number(row.pendingNum) || 0) === 0) {
+ uni.showToast({ title: '鏃犻渶鍐嶆帓浜�', icon: 'none' })
+ return;
+ }
+ nextTick(() => {
+ formDia.value?.openDialog(type, row)
+ })
+};
+
+// 鍗曟潯鍙栨秷鎺掍骇
+const handleCancel = (row) => {
+ if (!row) return
+ if (row.status == 3) {
+ uni.showToast({ title: '宸叉帓浜ф暟鎹笉鑳藉彇娑堟帓浜�', icon: 'none' })
+ return
+ }
+ uni.showModal({
+ title: '鍒犻櫎鎻愮ず',
+ content: '鏄惁纭鍙栨秷鎺掍骇锛�',
+ success: (res) => {
+ if (res.confirm) {
+ tableLoading.value = true
+ productionDispatchDelete([row.id])
+ .then(() => {
+ uni.showToast({ title: '鍙栨秷鎺掍骇鎴愬姛', icon: 'success' })
+ getList()
+ })
+ .finally(() => {
+ tableLoading.value = false
+ })
+ }
+ }
+ })
+}
+
+onMounted(() => {
+ getList();
+ // 鍒濆鍖栨樉绀哄瓧娈�
+ const cur = statusOptions.value.find(o => o.value === searchForm.value.status)
+ statusDisplay.value = cur ? cur.label : ''
+});
+</script>
+
+<style scoped lang="scss">
+.op-scheduling {
+ padding-bottom: 12px;
+}
+.search_form {
+ margin: 12px;
+ background: #fff;
+ border-radius: 8px;
+ padding: 10px;
+}
+.form-row {
+ display: flex;
+ gap: 12px;
+}
+.form-actions {
+ display: flex;
+ justify-content: flex-end;
+}
+.table_actions {
+ display: flex;
+ gap: 8px;
+ padding: 0 12px 10px 12px;
+ justify-content: flex-end;
+}
+.list_container {
+ padding: 0 12px;
+}
+.empty {
+ text-align: center;
+ color: #888;
+ padding: 20px 0;
+}
+.card_list {
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+.card_item {
+ background: #fff;
+ border-radius: 10px;
+ padding: 10px;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.05);
+}
+.card_header {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ margin-bottom: 6px;
+}
+.card_title { font-weight: 500; color: #333; margin-left: auto; }
+.card_body .row { display: flex; justify-content: space-between; padding: 4px 0; }
+.card_body .row.inline { display: flex; gap: 10px; flex-wrap: wrap; }
+.card_body .row.inline .col { min-width: 45%; display: flex; justify-content: space-between; }
+.label { color: #666; font-size: 12px; }
+.value { color: #333; font-size: 12px; }
+.card_actions { display: flex; justify-content: flex-end; gap: 8px; padding-top: 8px; }
+.ml8 { margin-left: 8px; }
+
+</style>
diff --git a/src/pages/productionManagement/productionCosting/index.vue b/src/pages/productionManagement/productionCosting/index.vue
new file mode 100644
index 0000000..8e6db58
--- /dev/null
+++ b/src/pages/productionManagement/productionCosting/index.vue
@@ -0,0 +1,107 @@
+<template>
+ <view class="prod-costing">
+ <PageHeader title="鐢熶骇鏍哥畻" />
+
+ <view class="search_form">
+ <u-form>
+
+ <view class="form-row">
+ <u-form-item label="鐢熶骇浜�" label-width="80">
+ <up-input v-model="searchForm.schedulingUserName" placeholder="璇疯緭鍏�" clearable @change="handleQuery" />
+ </u-form-item>
+ </view>
+ <view class="form-actions">
+ <u-button type="primary" size="small" @click="handleQuery">鎼滅储</u-button>
+
+ </view>
+ </u-form>
+ </view>
+
+ <view class="list_container">
+ <u-loading-icon v-if="tableLoading" text="鍔犺浇涓�..." />
+ <view v-else>
+ <view v-if="!tableData || tableData.length === 0" class="empty">鏆傛棤鏁版嵁</view>
+ <view v-else class="card_list">
+ <view v-for="item in tableData" :key="item.id" class="card_item">
+ <view class="card_header">
+ <text class="card_title">{{ item.projectName }}</text>
+ </view>
+ <view class="card_body">
+ <view class="row"><text class="label">鐢熶骇鏃ユ湡</text><text class="value">{{ item.schedulingDate }}</text></view>
+ <view class="row"><text class="label">鐢熶骇浜�</text><text class="value">{{ item.schedulingUserName }}</text></view>
+ <view class="row"><text class="label">鍚堝悓鍙�</text><text class="value">{{ item.salesContractNo }}</text></view>
+ <view class="row"><text class="label">瀹㈡埛鍚堝悓鍙�</text><text class="value">{{ item.customerContractNo }}</text></view>
+ <view class="row"><text class="label">瀹㈡埛鍚嶇О</text><text class="value">{{ item.customerName }}</text></view>
+ <view class="row"><text class="label">浜у搧澶х被</text><text class="value">{{ item.productCategory }}</text></view>
+ <view class="row"><text class="label">瑙勬牸鍨嬪彿</text><text class="value">{{ item.specificationModel }}</text></view>
+ <view class="row inline">
+ <view class="col"><text class="label">鍗曚綅</text><text class="value">{{ item.unit }}</text></view>
+ <view class="col"><text class="label">宸ュ簭</text><text class="value">{{ item.process }}</text></view>
+ <view class="col"><text class="label">鐢熶骇鏁伴噺</text><text class="value">{{ item.finishedNum }}</text></view>
+ <view class="col"><text class="label">宸ユ椂瀹氶</text><text class="value">{{ item.workHours }}</text></view>
+ <view class="col"><text class="label">宸ヨ祫</text><text class="value">{{ item.wages }}</text></view>
+ </view>
+ </view>
+ </view>
+ </view>
+
+
+ </view>
+ </view>
+
+
+ </view>
+</template>
+
+<script setup>
+import { onMounted, ref, reactive, toRefs } from "vue";
+import PageHeader from '@/components/PageHeader.vue'
+import { productionAccountingListPage } from "@/api/productionManagement/productionCosting.js";
+
+// state
+const tableData = ref([]);
+const tableLoading = ref(false);
+const page = reactive({ current: -1, size: -1 });
+const data = reactive({
+ searchForm: {
+ schedulingUserName: "",
+ },
+});
+const { searchForm } = toRefs(data);
+
+// 鏃犳棩鏈熺瓫閫�
+
+// 鏌ヨ锛堜笉鍒嗛〉锛屽浐瀹氫紶 -1锛�
+const handleQuery = () => { page.current = -1; page.size = -1; getList(); };
+const getList = () => {
+ tableLoading.value = true;
+ const params = { ...searchForm.value, ...page };
+ productionAccountingListPage(params).then((res) => {
+ tableLoading.value = false;
+ tableData.value = res.data.records || [];
+ }).catch(() => { tableLoading.value = false; })
+};
+
+onMounted(() => { getList(); });
+</script>
+
+<style scoped lang="scss">
+.prod-costing { padding-bottom: 12px; }
+.search_form { margin: 12px; background: #fff; border-radius: 8px; padding: 10px; }
+.form-row { display: flex; gap: 12px; }
+.form-actions { display: flex; justify-content: flex-end; }
+.ml8 { margin-left: 8px; }
+.list_container { padding: 0 12px; }
+.empty { text-align: center; color: #888; padding: 20px 0; }
+.card_list { display: flex; flex-direction: column; gap: 10px; }
+.card_item { background: #fff; border-radius: 10px; padding: 10px; box-shadow: 0 2px 8px rgba(0,0,0,0.05); }
+.card_header { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; }
+.card_title { font-weight: 500; color: #333; }
+.card_body .row { display: flex; justify-content: space-between; padding: 4px 0; }
+.card_body .row.inline { display: flex; gap: 10px; flex-wrap: wrap; }
+.card_body .row.inline .col { min-width: 45%; display: flex; justify-content: space-between; }
+.label { color: #666; font-size: 12px; }
+.value { color: #333; font-size: 12px; }
+.pagination { display: flex; align-items: center; justify-content: center; gap: 10px; padding: 12px 0; }
+.page_text { color: #666; font-size: 12px; }
+</style>
diff --git a/src/pages/productionManagement/productionDispatching/components/autoDispatchDia.vue b/src/pages/productionManagement/productionDispatching/components/autoDispatchDia.vue
new file mode 100644
index 0000000..7b2dfdc
--- /dev/null
+++ b/src/pages/productionManagement/productionDispatching/components/autoDispatchDia.vue
@@ -0,0 +1,381 @@
+<template>
+ <view>
+ <up-popup
+ v-model:show="dialogFormVisible"
+ mode="center"
+ border-radius="20"
+ :closeable="true"
+ @close="closeDia"
+ >
+ <view class="popup-content">
+ <view class="popup-header">
+ <text class="popup-title">鑷姩娲惧伐</text>
+ </view>
+
+ <view class="popup-body">
+ <view class="section-title">娲惧伐鍒楄〃</view>
+
+ <view class="card-list">
+ <view
+ v-for="(item, index) in dispatchList"
+ :key="index"
+ class="dispatch-card"
+ :class="{ 'even-card': index % 2 === 1 }"
+ >
+ <view class="card-header">
+ <text class="card-index">{{ index + 1 }}</text>
+ <text class="card-project">{{ item.projectName }}</text>
+ </view>
+
+ <view class="card-content">
+ <view class="info-row">
+ <text class="info-label">鍚堝悓鍙凤細</text>
+ <text class="info-value">{{ item.salesContractNo }}</text>
+ </view>
+
+ <view class="info-row">
+ <text class="info-label">瀹㈡埛锛�</text>
+ <text class="info-value">{{ item.customerName }}</text>
+ </view>
+
+ <view class="info-row">
+ <text class="info-label">浜у搧绫诲埆锛�</text>
+ <text class="info-value">{{ item.productCategory }}</text>
+ </view>
+
+ <view class="info-row">
+ <text class="info-label">瑙勬牸鍨嬪彿锛�</text>
+ <text class="info-value">{{ item.specificationModel }}</text>
+ </view>
+
+ <view class="info-row">
+ <text class="info-label">缁戝畾鏈哄櫒锛�</text>
+ <text class="info-value">{{ item.speculativeTradingName }}</text>
+ </view>
+
+ <view class="quantity-row">
+ <view class="quantity-item">
+ <text class="quantity-label">鎬绘暟閲忥細</text>
+ <text class="quantity-value">{{ item.quantity }}</text>
+ </view>
+
+ <view class="quantity-item">
+ <text class="quantity-label">宸叉帓浜э細</text>
+ <text class="quantity-value">{{ item.schedulingNum }}</text>
+ </view>
+
+ <view class="quantity-item">
+ <text class="quantity-label">寰呮帓浜э細</text>
+ <text class="quantity-value">{{ item.pendingQuantity }}</text>
+ </view>
+ </view>
+
+ <view class="scheduling-row">
+ <text class="scheduling-label">鏈鎺掍骇锛�</text>
+ <up-number-box
+ v-model="item.schedulingNum"
+ :min="0"
+ :max="item.pendingQuantity"
+ :step="1"
+ :precision="0"
+ size="mini"
+ @change="(value) => changeCurrentNum(value, item)"
+ class="scheduling-input"
+ />
+ </view>
+ </view>
+ </view>
+ </view>
+
+ <view v-if="dispatchList.length === 0" class="empty-state">
+ <text class="empty-text">鏆傛棤娲惧伐鏁版嵁</text>
+ </view>
+ </view>
+
+ <view class="popup-footer">
+ <up-button type="primary" @click="submitForm" class="confirm-btn">纭娲惧伐</up-button>
+ <up-button @click="closeDia" class="cancel-btn">鍙栨秷</up-button>
+ </view>
+ </view>
+ </up-popup>
+ </view>
+</template>
+
+<script setup>
+import { ref, reactive, toRefs } from "vue";
+import { productionDispatchList } from "@/api/productionManagement/productionOrder.js";
+
+const emit = defineEmits(['close'])
+
+const dialogFormVisible = ref(false);
+const operationType = ref('')
+
+const data = reactive({
+ form: {},
+ dispatchList: [], // 娲惧伐鍒楄〃鏁版嵁
+});
+
+const { form, dispatchList } = toRefs(data);
+
+// 琛ㄦ牸琛屾牱寮�
+const tableRowClassName = ({ rowIndex }) => {
+ if (rowIndex % 2 === 1) {
+ return 'even-row'
+ }
+ return ''
+}
+
+// 淇敼鏈鎺掍骇鏁伴噺
+const changeCurrentNum = (value, row) => {
+ if (value > row.pendingQuantity) {
+ row.schedulingNum = row.pendingQuantity
+ uni.$u.toast('鎺掍骇鏁伴噺涓嶅彲澶т簬寰呮帓浜ф暟閲�')
+ }
+}
+
+// 鎵撳紑寮规
+const openDialog = (rows) => {
+ dialogFormVisible.value = true;
+
+ console.log('鎺ユ敹鍒颁紶鍏ョ殑鏁版嵁:', rows);
+ console.log('浼犲叆鏁版嵁鏁伴噺:', rows.length);
+
+ // 澶勭悊浼犲叆鐨勬暟鎹�
+ dispatchList.value = rows.map((row, index) => ({
+ ...row,
+ schedulingNum: 0, // 鍒濆鍖栨湰娆℃帓浜ф暟閲忎负0
+ pendingQuantity: (Number(row.quantity) || 0) - (Number(row.schedulingNum) || 0) // 璁$畻寰呮帓浜ф暟閲�
+ }))
+
+ console.log('澶勭悊鍚庣殑娲惧伐鍒楄〃:', dispatchList.value);
+ console.log('娲惧伐鍒楄〃鏁伴噺:', dispatchList.value.length);
+}
+
+// 鎻愪氦琛ㄥ崟
+const submitForm = () => {
+ // 妫�鏌ユ槸鍚︽湁鎺掍骇鏁版嵁
+ const hasSchedulingData = dispatchList.value.some(item => item.schedulingNum > 0)
+ if (!hasSchedulingData) {
+ uni.$u.toast('璇疯嚦灏戜负涓�鏉¤褰曡缃帓浜ф暟閲�')
+ return
+ }
+
+ // 鏋勯�犳彁浜ゆ暟鎹� - 鐩存帴浼犻�掓暟缁勶紝涓嶈繃婊�
+ const submitData = dispatchList.value
+
+ console.log('鎻愪氦鑷姩娲惧伐鏁版嵁:', submitData)
+
+ // 璋冪敤API锛堣繖閲岄渶瑕佹牴鎹疄闄呮帴鍙h皟鏁达級
+ productionDispatchList(submitData).then(res => {
+ uni.$u.toast(res.msg || '娲惧伐鎴愬姛');
+ closeDia();
+ }).catch(err => {
+ uni.$u.toast('娲惧伐澶辫触');
+ console.error('娲惧伐澶辫触:', err);
+ })
+}
+
+// 鍏抽棴寮规
+const closeDia = () => {
+ dialogFormVisible.value = false;
+ dispatchList.value = []
+ emit('close')
+};
+
+defineExpose({
+ openDialog,
+});
+</script>
+
+<style lang="scss" scoped>
+.popup-content {
+ width: 90vw;
+ max-width: 1200px;
+ background: #fff;
+ border-radius: 20rpx;
+ overflow: hidden;
+}
+
+.popup-header {
+ padding: 30rpx;
+ border-bottom: 1rpx solid #f0f0f0;
+ text-align: center;
+}
+
+.popup-title {
+ font-size: 36rpx;
+ font-weight: bold;
+ color: #333;
+}
+
+.popup-body {
+ padding: 30rpx;
+ height: 60vh;
+ overflow-y: auto;
+ overflow-x: hidden;
+ -webkit-overflow-scrolling: touch;
+ max-height: 60vh;
+}
+
+.section-title {
+ font-size: 32rpx;
+ font-weight: bold;
+ color: #333;
+ margin-bottom: 20rpx;
+ flex-shrink: 0;
+}
+
+.card-list {
+ display: flex;
+ flex-direction: column;
+ gap: 20rpx;
+ min-height: 0;
+ overflow-y: auto;
+ max-height: 60vh;
+}
+
+.dispatch-card {
+ background: #f8f9fa;
+ border-radius: 12rpx;
+ padding: 24rpx;
+ border: 1rpx solid #e9ecef;
+ transition: all 0.3s ease;
+ flex-shrink: 0;
+}
+
+.dispatch-card:hover {
+ box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
+ transform: translateY(-2rpx);
+}
+
+.even-card {
+ background: #ffffff;
+}
+
+.card-header {
+ display: flex;
+ align-items: center;
+ margin-bottom: 20rpx;
+ padding-bottom: 16rpx;
+ border-bottom: 1rpx solid #e9ecef;
+}
+
+.card-index {
+ background: #1890ff;
+ color: white;
+ width: 40rpx;
+ height: 40rpx;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 24rpx;
+ font-weight: bold;
+ margin-right: 16rpx;
+}
+
+.card-project {
+ font-size: 28rpx;
+ font-weight: bold;
+ color: #333;
+ flex: 1;
+}
+
+.card-content {
+ display: flex;
+ flex-direction: column;
+ gap: 12rpx;
+}
+
+.info-row {
+ display: flex;
+ align-items: center;
+}
+
+.info-label {
+ font-size: 26rpx;
+ color: #666;
+ width: 140rpx;
+ flex-shrink: 0;
+}
+
+.info-value {
+ font-size: 26rpx;
+ color: #333;
+ flex: 1;
+}
+
+.quantity-row {
+ display: flex;
+ gap: 20rpx;
+ margin: 8rpx 0;
+}
+
+.quantity-item {
+ display: flex;
+ align-items: center;
+ background: #f8f9fa;
+ padding: 8rpx 16rpx;
+ border-radius: 6rpx;
+ border: 1rpx solid #e9ecef;
+}
+
+.quantity-label {
+ font-size: 24rpx;
+ color: #666;
+ margin-right: 8rpx;
+}
+
+.quantity-value {
+ font-size: 24rpx;
+ color: #1890ff;
+ font-weight: bold;
+}
+
+.scheduling-row {
+ display: flex;
+ align-items: center;
+ margin-top: 12rpx;
+ padding-top: 12rpx;
+ border-top: 1rpx dashed #e9ecef;
+}
+
+.scheduling-label {
+ font-size: 26rpx;
+ color: #333;
+ font-weight: bold;
+ margin-right: 16rpx;
+ width: 140rpx;
+ flex-shrink: 0;
+}
+
+.scheduling-input {
+ flex: 1;
+}
+
+.empty-state {
+ text-align: center;
+ padding: 60rpx 30rpx;
+ color: #999;
+}
+
+.empty-text {
+ font-size: 28rpx;
+}
+
+.popup-footer {
+ padding: 30rpx;
+ border-top: 1rpx solid #f0f0f0;
+ display: flex;
+ justify-content: center;
+ gap: 20rpx;
+}
+
+.confirm-btn {
+ width: 200rpx;
+}
+
+.cancel-btn {
+ width: 200rpx;
+}
+</style>
\ No newline at end of file
diff --git a/src/pages/productionManagement/productionDispatching/index.vue b/src/pages/productionManagement/productionDispatching/index.vue
index 87c3da0..6168994 100644
--- a/src/pages/productionManagement/productionDispatching/index.vue
+++ b/src/pages/productionManagement/productionDispatching/index.vue
@@ -2,7 +2,7 @@
<view class="production-dispatching">
<!-- 浣跨敤閫氱敤椤甸潰澶撮儴缁勪欢 -->
<PageHeader title="鐢熶骇娲惧伐" @back="goBack" />
-
+
<!-- 鐐掓満鐘舵�佸睍绀� -->
<view class="machines-section">
<view class="section-title">鐐掓満鐘舵��</view>
@@ -33,6 +33,28 @@
</view>
</view>
</view>
+ <!-- 鎹熻�楃巼璁剧疆 -->
+ <view class="loss-rate-section">
+ <view class="section-title">鎹熻�楃巼璁剧疆</view>
+ <view class="loss-rate-content">
+ <view class="loss-rate-item">
+ <up-button
+ class="loss-rate-btn"
+ type="primary"
+ plain
+ size="small"
+ @click="showLossRateSheet = true"
+ >{{ lossRate ? `鎹熻�楃巼: ${lossRate}%` : '璇烽�夋嫨鎹熻�楃巼' }}</up-button>
+ <up-action-sheet
+ :show="showLossRateSheet"
+ :actions="lossRateOptions"
+ @select="onLossRateSelect"
+ title="閫夋嫨鎹熻�楃巼"
+ @close="showLossRateSheet = false"
+ />
+ </view>
+ </view>
+ </view>
<view class="save-section">
<up-button type="primary" @click="saveMachineTotals" size="normal" class="save-btn">淇濆瓨鐐掓満璁剧疆</up-button>
</view>
@@ -71,46 +93,41 @@
</view>
<!-- 鎵归噺鎿嶄綔鍖哄煙 -->
- <view v-if="showBatchActions" class="batch-actions-section">
+ <view class="batch-actions-section" v-if="showBatchActions">
<view class="batch-info">
- <text class="batch-count">宸查�夋嫨 {{ selectedItems.length }} 涓」鐩�</text>
+ <text class="batch-text">宸查�夋嫨 {{ selectedItems.length }} 涓」鐩�</text>
</view>
<view class="batch-buttons">
- <up-button type="primary" size="small" @click="handleAutoDispatch" class="batch-btn">
- <up-icon name="play-circle" size="16" color="#ffffff"></up-icon>
- 鑷姩娲惧崟
- </up-button>
- <up-button type="default" size="small" @click="clearSelection" class="batch-btn">
- <up-icon name="close-circle" size="16" color="#6c757d"></up-icon>
- 鍙栨秷閫夋嫨
- </up-button>
+ <up-button type="primary" size="small" @click="handleAutoDispatch" class="batch-btn">鑷姩娲惧崟</up-button>
+ <up-button type="default" size="small" @click="clearSelection" class="batch-btn">鍙栨秷閫夋嫨</up-button>
</view>
</view>
-
+
<!-- 鍏ㄩ�夋搷浣滃尯鍩� -->
- <view v-if="tableData.length > 0" class="select-all-section">
- <view class="select-all-checkbox" @click="toggleAllSelection">
- <up-icon
- :name="isAllSelected ? 'checkbox-mark' : 'circle'"
- :color="isAllSelected ? '#409eff' : '#c0c4cc'"
- size="18"
- ></up-icon>
- <text class="select-all-text">{{ isAllSelected ? '鍙栨秷鍏ㄩ��' : '鍏ㄩ��' }}</text>
+ <view class="select-all-section" v-if="tableData.length > 0">
+ <view class="select-all-content">
+ <up-checkbox
+ v-model="isAllSelected"
+ @change="toggleAllSelection"
+ label="鍏ㄩ��"
+ class="select-all-checkbox"
+ :disabled="tableData.length === 0 || tableData.filter(item => item.pendingQuantity > 0 && item.speculativeTradingName).length === 0"
+ />
</view>
- <text class="select-all-hint">锛堜粎閫夋嫨寰呮帓鏁伴噺澶т簬0鐨勯」鐩級</text>
</view>
-
+
<!-- 鐢熶骇娲惧伐鍒楄〃 -->
<view class="ledger-list" v-if="tableData.length > 0">
<view v-for="(item, index) in tableData" :key="item.id || index" class="list-item">
<view class="ledger-item">
<!-- 閫夋嫨澶嶉�夋 -->
- <view class="item-checkbox" @click="toggleItemSelection(item)">
- <up-icon
- :name="selectedItems.includes(item.id) ? 'checkbox-mark' : 'circle'"
- :color="selectedItems.includes(item.id) ? '#409eff' : '#c0c4cc'"
- size="18"
- ></up-icon>
+ <view class="item-checkbox">
+ <up-checkbox
+ :model-value="selectedItems.some(selected => selected.id === item.id)"
+ @change="(checked) => toggleItemSelection(item, checked)"
+ :disabled="item.pendingQuantity <= 0 || !item.speculativeTradingName"
+ shape="circle"
+ />
</view>
<view class="item-content">
@@ -146,6 +163,10 @@
<text class="detail-value">{{ item.specificationModel }}</text>
</view>
<view class="detail-row">
+ <text class="detail-label">缁戝畾鏈哄櫒</text>
+ <text class="detail-value">{{ item.speculativeTradingName }}</text>
+ </view>
+ <view class="detail-row">
<text class="detail-label">鍗曚綅</text>
<text class="detail-value">{{ item.unit }}</text>
</view>
@@ -165,14 +186,14 @@
<!-- 鎿嶄綔鎸夐挳鍖哄煙 -->
<view class="action-buttons">
<up-button
- type="primary"
- size="small"
- @click="handleDispatch(item)"
- class="action-btn"
- :disabled="item.pendingQuantity <= 0"
- >
- {{ item.pendingQuantity <= 0 ? '鏃犻渶娲惧伐' : '鐢熶骇娲惧伐' }}
- </up-button>
+ type="primary"
+ size="small"
+ @click="handleDispatch(item)"
+ class="action-btn"
+ :disabled="item.pendingQuantity <= 0 || !item.speculativeTradingName"
+ >
+ {{ item.pendingQuantity <= 0 ? '鏃犻渶娲惧伐' : !item.speculativeTradingName ? '鏈粦瀹氭満鍣�' : '鐢熶骇娲惧伐' }}
+ </up-button>
</view>
</view>
</view>
@@ -188,16 +209,20 @@
<!-- 娲惧伐寮圭獥 -->
<DispatchModal ref="dispatchModalRef" @confirm="handleDispatchConfirm" />
+
+ <!-- 鑷姩娲惧崟寮圭獥 -->
+ <AutoDispatchDia ref="autoDispatchDia" />
</view>
</template>
<script setup>
-import { ref, reactive, toRefs, getCurrentInstance } from "vue";
+import { ref, reactive, toRefs, getCurrentInstance, nextTick } from "vue";
import { onShow } from '@dcloudio/uni-app';
import dayjs from "dayjs";
-import {schedulingListPage, schedulingList, addSpeculatTrading, updateSpeculatTrading} from "@/api/productionManagement/productionOrder.js";
+import {schedulingListPage, schedulingList, addSpeculatTrading, updateSpeculatTrading, getLossRate, addLossRate, updateLossRate} from "@/api/productionManagement/productionOrder.js";
import PageHeader from "@/components/PageHeader.vue";
import DispatchModal from "./components/DispatchModal.vue";
+import AutoDispatchDia from "./components/autoDispatchDia.vue";
const { proxy } = getCurrentInstance();
@@ -207,10 +232,10 @@
// 鍒楄〃鏁版嵁
const tableData = ref([]);
-// 鎵归噺閫夋嫨鐩稿叧鏁版嵁
-const selectedItems = ref([]); // 閫変腑鐨勯」鐩甀D鏁扮粍
-const isAllSelected = ref(false); // 鏄惁鍏ㄩ��
-const showBatchActions = ref(false); // 鏄惁鏄剧ず鎵归噺鎿嶄綔鍖哄煙
+// 閫夋嫨鐩稿叧鏁版嵁
+const selectedItems = ref([]);
+const isAllSelected = ref(false);
+const showBatchActions = ref(false);
// 鎼滅储琛ㄥ崟鏁版嵁
const data = reactive({
@@ -261,8 +286,23 @@
// 鏄惁鏈夋煡璇㈡暟鎹紙鐢ㄤ簬鍒ゆ柇鏄柊澧炶繕鏄慨鏀癸級
const hasQueryData = ref(false);
+// 鎹熻�楃巼鐩稿叧鏁版嵁
+const lossRate = ref(""); // 褰撳墠閫夋嫨鐨勬崯鑰楃巼
+const showLossRateSheet = ref(false); // 鎺у埗鎹熻�楃巼閫夋嫨闈㈡澘鏄剧ず
+const lossRateOptions = ref([
+ { name: "6%", value: "6" },
+ { name: "7%", value: "7" },
+ { name: "8%", value: "8" },
+ { name: "9%", value: "9" },
+ { name: "10%", value: "10" }
+]);
+const lossRateData = ref(null); // 鎹熻�楃巼鏌ヨ杩斿洖鐨勬暟鎹�
+
// 娲惧伐寮圭獥寮曠敤
const dispatchModalRef = ref();
+
+// 鑷姩娲惧崟寮圭獥寮曠敤
+const autoDispatchDia = ref();
// 閫氱敤鎻愮ず鍑芥暟
const showLoadingToast = (message) => {
@@ -328,6 +368,28 @@
});
};
+// 鎹熻�楃巼閫夋嫨浜嬩欢
+const onLossRateSelect = (action) => {
+ lossRate.value = action.value;
+ showLossRateSheet.value = false;
+ console.log('閫夋嫨浜嗘崯鑰楃巼:', action.name, '鍊�:', action.value);
+};
+
+// 鑾峰彇鎹熻�楃巼鏁版嵁
+const getLossRateData = () => {
+ getLossRate().then((res) => {
+ if (res.data) {
+ lossRateData.value = res.data;
+ // 璁剧疆褰撳墠閫夋嫨鐨勬崯鑰楃巼
+ if (res.data.rate !== null && res.data.rate !== undefined) {
+ lossRate.value = res.data.rate.toString();
+ }
+ }
+ }).catch(err => {
+ console.error('鑾峰彇鎹熻�楃巼澶辫触:', err);
+ });
+};
+
// 鑾峰彇鍒楄〃鏁版嵁
const getList = () => {
loading.value = true;
@@ -340,14 +402,17 @@
closeToast();
tableData.value = (res.data.records || []).map(item => ({
- ...item,
- pendingQuantity: (Number(item.quantity) || 0) - (Number(item.schedulingNum) || 0)
- }));
+ ...item,
+ pendingQuantity: (Number(item.quantity) || 0) - (Number(item.schedulingNum) || 0)
+ })).filter(item => item.pendingQuantity > 0);
page.total = res.data.total || 0;
// 鑾峰彇鐐掓満鏁版嵁
getMachineProductionData();
+
+ // 鑾峰彇鎹熻�楃巼鏁版嵁
+ getLossRateData();
}).catch(() => {
loading.value = false;
@@ -364,6 +429,14 @@
if (item.pendingQuantity <= 0) {
uni.showToast({
title: '璇ラ」鐩棤闇�鍐嶆淳宸�',
+ icon: 'none'
+ });
+ return;
+ }
+
+ if (!item.speculativeTradingName) {
+ uni.showToast({
+ title: '璇ラ」鐩湭缁戝畾鏈哄櫒锛屾棤娉曟淳宸�',
icon: 'none'
});
return;
@@ -392,6 +465,51 @@
workLoad: machineTotal[`m${machineId}`] || 0,
currentWorkLoad: machineInProduction[`m${machineId}`] || 0
};
+};
+
+// 淇濆瓨鎹熻�楃巼璁剧疆
+const saveLossRate = () => {
+ if (!lossRate.value) {
+ console.log('鏈�夋嫨鎹熻�楃巼锛岃烦杩囦繚瀛�');
+ return Promise.resolve();
+ }
+
+ const lossRateDataToSave = {
+ rate: parseFloat(lossRate.value) || 0
+ };
+
+ // 濡傛灉鏈夋煡璇㈠埌鐨勬崯鑰楃巼鏁版嵁锛岃鏄庢槸淇敼鎿嶄綔锛岄渶瑕佷紶閫抜d
+ if (lossRateData.value && lossRateData.value.id) {
+ lossRateDataToSave.id = lossRateData.value.id;
+ }
+
+ console.log('淇濆瓨鎹熻�楃巼鏁版嵁:', lossRateDataToSave);
+
+ // 鏍规嵁鏄惁鏈夋崯鑰楃巼鏁版嵁鍐冲畾璋冪敤鏂板鎺ュ彛杩樻槸淇敼鎺ュ彛
+ const saveLossApi = lossRateData.value && lossRateData.value.id ? updateLossRate : addLossRate;
+ const successMessage = lossRateData.value && lossRateData.value.id ? '鎹熻�楃巼淇敼鎴愬姛' : '鎹熻�楃巼鏂板鎴愬姛';
+
+ return saveLossApi(lossRateDataToSave).then(res => {
+ console.log('鎹熻�楃巼淇濆瓨鎴愬姛:', res);
+ uni.showToast({
+ title: successMessage,
+ icon: 'success'
+ });
+
+ // 鏇存柊鎹熻�楃巼鏁版嵁
+ if (res.data) {
+ lossRateData.value = res.data;
+ }
+
+ return res;
+ }).catch(err => {
+ console.error('鎹熻�楃巼淇濆瓨澶辫触:', err);
+ uni.showToast({
+ title: '鎹熻�楃巼淇濆瓨澶辫触',
+ icon: 'none'
+ });
+ throw err;
+ });
};
// 淇濆瓨鐐掓満鎬婚噺璁剧疆
@@ -425,9 +543,15 @@
console.log(`璋冪敤鎺ュ彛: ${hasQueryData.value ? '淇敼' : '鏂板'}`);
- // 璋冪敤鍚庣API淇濆瓨
- saveApi(saveData).then(res => {
- proxy.$message.success(successMessage);
+ // 鍏堜繚瀛樻崯鑰楃巼锛屽啀淇濆瓨鐐掓満璁剧疆
+ saveLossRate().then(() => {
+ // 璋冪敤鍚庣API淇濆瓨鐐掓満璁剧疆
+ return saveApi(saveData);
+ }).then(res => {
+ uni.showToast({
+ title: successMessage,
+ icon: 'success'
+ });
console.log('淇濆瓨鎴愬姛:', res);
// 淇濆瓨鎴愬姛鍚庯紝璁剧疆hasQueryData涓簍rue锛屼笅娆′繚瀛樺皢璋冪敤淇敼鎺ュ彛
@@ -435,61 +559,69 @@
hasQueryData.value = true;
}
}).catch(err => {
- proxy.$message.error('淇濆瓨澶辫触');
+ uni.showToast({
+ title: '淇濆瓨澶辫触',
+ icon: 'none'
+ });
console.error('淇濆瓨澶辫触:', err);
});
};
-// 鎵归噺閫夋嫨鐩稿叧鍑芥暟
-
-// 鍒囨崲鍗曚釜椤圭洰鐨勯�夋嫨鐘舵��
-const toggleItemSelection = (item) => {
- const itemId = item.id;
- const index = selectedItems.value.indexOf(itemId);
+// 鍒囨崲鍗曚釜椤圭洰閫夋嫨鐘舵��
+const toggleItemSelection = (item, checked) => {
+ // 浠呭厑璁搁�夋嫨宸茬粦瀹氭満鍣ㄤ笖寰呮淳鏁伴噺>0鐨勯」鐩�
+ if (!item.speculativeTradingName || item.pendingQuantity <= 0) return;
- if (index > -1) {
- // 濡傛灉宸查�変腑锛屽垯鍙栨秷閫夋嫨
- selectedItems.value.splice(index, 1);
+ console.log('鍒囨崲閫夋嫨鐘舵��:', item.id, checked);
+
+ // 浣跨敤鏇翠弗鏍肩殑姣旇緝閫昏緫锛岀‘淇滻D鍞竴鎬�
+ const index = selectedItems.value.findIndex(selected => {
+ // 娣卞害姣旇緝瀵硅薄锛岀‘淇濇槸鍚屼竴涓」鐩�
+ return JSON.stringify(selected) === JSON.stringify(item);
+ });
+
+ if (checked) {
+ // 濡傛灉閫変腑涓斾笉鍦ㄩ�変腑鍒楄〃涓紝鍒欐坊鍔�
+ if (index === -1) {
+ selectedItems.value.push({...item}); // 鍒涘缓鏂板璞★紝閬垮厤寮曠敤闂
+ console.log('娣诲姞椤圭洰鍚庨�変腑鏁伴噺:', selectedItems.value.length);
+ }
} else {
- // 濡傛灉鏈�変腑锛屽垯娣诲姞閫夋嫨
- selectedItems.value.push(itemId);
+ // 濡傛灉鍙栨秷閫変腑涓斿湪閫変腑鍒楄〃涓紝鍒欑Щ闄�
+ if (index > -1) {
+ selectedItems.value.splice(index, 1);
+ console.log('绉婚櫎椤圭洰鍚庨�変腑鏁伴噺:', selectedItems.value.length);
+ }
}
- // 鏇存柊鍏ㄩ�夌姸鎬�
+ console.log('褰撳墠閫変腑椤圭洰鍒楄〃:', selectedItems.value.map(s => s.id));
updateAllSelectedStatus();
- // 鏇存柊鎵归噺鎿嶄綔鍖哄煙鏄剧ず鐘舵��
updateBatchActionsVisibility();
};
// 鍒囨崲鍏ㄩ�夌姸鎬�
const toggleAllSelection = () => {
if (isAllSelected.value) {
- // 鍙栨秷鍏ㄩ��
selectedItems.value = [];
} else {
- // 鍏ㄩ��
- selectedItems.value = tableData.value
- .filter(item => item.pendingQuantity > 0) // 鍙�夋嫨寰呮帓鏁伴噺澶т簬0鐨勯」鐩�
- .map(item => item.id);
+ selectedItems.value = tableData.value.filter(item => item.pendingQuantity > 0 && item.speculativeTradingName).map(item => ({ ...item }));
}
-
isAllSelected.value = !isAllSelected.value;
updateBatchActionsVisibility();
};
// 鏇存柊鍏ㄩ�夌姸鎬�
const updateAllSelectedStatus = () => {
- const selectableItems = tableData.value.filter(item => item.pendingQuantity > 0);
- if (selectableItems.length === 0) {
+ const selectableItems = tableData.value.filter(item => item.pendingQuantity > 0 && item.speculativeTradingName);
+ if (selectableItems.length > 0 && selectedItems.value.length === selectableItems.length &&
+ selectableItems.every(item => selectedItems.value.some(selected => selected.id === item.id))) {
+ isAllSelected.value = true;
+ } else {
isAllSelected.value = false;
- return;
}
-
- isAllSelected.value = selectedItems.value.length === selectableItems.length &&
- selectableItems.every(item => selectedItems.value.includes(item.id));
};
-// 鏇存柊鎵归噺鎿嶄綔鍖哄煙鏄剧ず鐘舵��
+// 鏇存柊鎵归噺鎿嶄綔鏄剧ず鐘舵��
const updateBatchActionsVisibility = () => {
showBatchActions.value = selectedItems.value.length > 0;
};
@@ -503,80 +635,36 @@
// 鑾峰彇閫変腑鐨勯」鐩�
const getSelectedItems = () => {
- return tableData.value.filter(item => selectedItems.value.includes(item.id));
+ return selectedItems.value;
};
-// 鑷姩娲惧崟鍔熻兘
+// 澶勭悊鑷姩娲惧崟
const handleAutoDispatch = () => {
- const selectedItemsList = getSelectedItems();
-
- if (selectedItemsList.length === 0) {
+ if (selectedItems.value.length === 0) {
uni.showToast({
- title: '璇峰厛閫夋嫨瑕佹淳宸ョ殑椤圭洰',
+ title: '璇烽�夋嫨瑕佹淳宸ョ殑椤圭洰',
icon: 'none'
});
return;
}
- // 妫�鏌ユ槸鍚︽湁椤圭洰寰呮帓鏁伴噺涓嶈冻
- const invalidItems = selectedItemsList.filter(item => item.pendingQuantity <= 0);
- if (invalidItems.length > 0) {
+ // 妫�鏌ユ槸鍚︽墍鏈夐�変腑椤圭洰閮芥湁缁戝畾鏈哄櫒
+ const unboundItems = selectedItems.value.filter(item => !item.speculativeTradingName);
+ if (unboundItems.length > 0) {
uni.showToast({
- title: `鏈�${invalidItems.length}涓」鐩棤闇�娲惧伐锛屽凡鑷姩杩囨护`,
- icon: 'none'
- });
- }
-
- // 杩囨护鎺夊緟鎺掓暟閲忎笉瓒崇殑椤圭洰
- const validItems = selectedItemsList.filter(item => item.pendingQuantity > 0);
-
- if (validItems.length === 0) {
- uni.showToast({
- title: '娌℃湁鍙淳宸ョ殑椤圭洰',
+ title: '鎵�閫夐」鐩腑鏈夋湭缁戝畾鏈哄櫒鐨勯」鐩紝鏃犳硶鑷姩娲惧崟',
icon: 'none'
});
return;
}
- uni.showModal({
- title: '纭鑷姩娲惧崟',
- content: `纭畾瑕佸閫変腑鐨�${validItems.length}涓」鐩繘琛岃嚜鍔ㄦ淳鍗曞悧锛焋,
- success: (res) => {
- if (res.confirm) {
- executeAutoDispatch(validItems);
- }
- }
- });
-};
-
-// 鎵ц鑷姩娲惧崟
-const executeAutoDispatch = (items) => {
- showLoadingToast('鑷姩娲惧崟涓�...');
-
- // 妯℃嫙鑷姩娲惧崟杩囩▼
- setTimeout(() => {
- closeToast();
-
- // 杩欓噷搴旇璋冪敤瀹為檯鐨勮嚜鍔ㄦ淳鍗旳PI
- // 鏆傛椂浣跨敤妯℃嫙鎴愬姛
- uni.showToast({
- title: `鎴愬姛涓�${items.length}涓」鐩畬鎴愯嚜鍔ㄦ淳鍗昤,
- icon: 'success'
- });
-
- // 娓呯┖閫夋嫨
- clearSelection();
- // 鍒锋柊鍒楄〃
- getList();
-
- console.log('鑷姩娲惧崟椤圭洰:', items);
- }, 1500);
+ // 纭繚浼犻�掔殑鏄畬鏁寸殑閫変腑椤圭洰鏁扮粍
+ autoDispatchDia.value?.openDialog([...selectedItems.value]);
};
// 椤甸潰鏄剧ず鏃跺姞杞芥暟鎹�
onShow(() => {
getList();
- // 娓呯┖閫夋嫨鐘舵��
clearSelection();
});
</script>
@@ -588,16 +676,63 @@
padding: 20rpx;
}
+// 鎹熻�楃巼璁剧疆鍖哄煙
+.loss-rate-section {
+ background: #ffffff;
+ border: 1rpx solid #e4e7ed;
+ border-radius: 12rpx;
+ padding: 32rpx;
+ margin-top: 24rpx;
+ margin-bottom: 32rpx;
+ box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08);
+}
+
+.loss-rate-section .section-title {
+ font-size: 32rpx;
+ font-weight: 600;
+ color: #303133;
+ margin-bottom: 20rpx;
+}
+
+.loss-rate-section .loss-rate-content {
+ display: flex;
+ flex-direction: column;
+ gap: 24rpx;
+}
+
+.loss-rate-section .loss-rate-content .loss-rate-item {
+ display: flex;
+ align-items: center;
+ gap: 24rpx;
+}
+
+.loss-rate-section .loss-rate-content .loss-rate-label {
+ font-size: 30rpx;
+ font-weight: 500;
+ color: #303133;
+ min-width: 140rpx;
+ white-space: nowrap;
+}
+
+.loss-rate-section .loss-rate-content .loss-rate-btn {
+ min-width: 260rpx;
+ font-size: 28rpx;
+ height: 64rpx;
+ line-height: 64rpx;
+ border-radius: 8rpx;
+ font-weight: 500;
+}
+
// 鐐掓満鐘舵�佸尯鍩�
.machines-section {
margin-bottom: 30rpx;
-
- .section-title {
- font-size: 32rpx;
- font-weight: 600;
- color: #303133;
- margin-bottom: 20rpx;
- }
+}
+
+.machines-section .section-title {
+ font-size: 32rpx;
+ font-weight: 600;
+ color: #303133;
+ margin-bottom: 20rpx;
}
.machines-grid {
@@ -771,10 +906,10 @@
align-items: center;
padding: 12rpx 0;
border-bottom: 1rpx solid #f5f5f5;
-
- &:last-child {
- border-bottom: none;
- }
+}
+
+.detail-row:last-child {
+ border-bottom: none;
}
.detail-label {
@@ -786,16 +921,16 @@
font-size: 26rpx;
color: #303133;
font-weight: 500;
-
- &.highlight {
- color: #ff6b35;
- font-weight: 600;
- }
-
- &.danger {
- color: #ee0a24;
- font-weight: 600;
- }
+}
+
+.detail-value.highlight {
+ color: #ff6b35;
+ font-weight: 600;
+}
+
+.detail-value.danger {
+ color: #ee0a24;
+ font-weight: 600;
}
.action-buttons {
@@ -808,6 +943,69 @@
min-width: 180rpx;
}
+// 鎵归噺鎿嶄綔鍖哄煙鏍峰紡
+.batch-actions-section {
+ background: #e8f4ff;
+ border: 1rpx solid #409eff;
+ border-radius: 12rpx;
+ padding: 20rpx 24rpx;
+ margin-bottom: 24rpx;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.batch-actions-section .batch-text {
+ font-size: 28rpx;
+ font-weight: 600;
+ color: #409eff;
+}
+
+.batch-actions-section .batch-buttons {
+ display: flex;
+ gap: 16rpx;
+}
+
+.batch-actions-section .batch-btn {
+ min-width: 140rpx;
+}
+
+// 鍏ㄩ�夋搷浣滃尯鍩熸牱寮�
+.select-all-section {
+ background: #ffffff;
+ border-radius: 12rpx;
+ padding: 20rpx 24rpx;
+ margin-bottom: 16rpx;
+ border: 1rpx solid #e4e7ed;
+}
+
+.select-all-section .select-all-content {
+ display: flex;
+ align-items: center;
+}
+
+.select-all-section .select-all-checkbox {
+ font-size: 28rpx;
+ font-weight: 500;
+}
+
+// 鍒楄〃椤归�夋嫨妗嗘牱寮�
+.ledger-item {
+ display: flex;
+ align-items: flex-start;
+ padding: 0;
+}
+
+.item-checkbox {
+ padding: 24rpx 16rpx 0 24rpx;
+ display: flex;
+ align-items: center;
+}
+
+.item-content {
+ flex: 1;
+}
+
// 绌虹姸鎬�
.no-data {
padding: 100rpx 0;
@@ -818,88 +1016,6 @@
font-size: 28rpx;
color: #909399;
margin-top: 20rpx;
-}
-
-// 鎵归噺鎿嶄綔鍖哄煙鏍峰紡
-.batch-actions-section {
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
- border-radius: 16rpx;
- padding: 24rpx;
- margin-bottom: 24rpx;
- box-shadow: 0 8rpx 24rpx rgba(102, 126, 234, 0.3);
- color: #ffffff;
-}
-
-.batch-info {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 20rpx;
-}
-
-.batch-count {
- font-size: 28rpx;
- font-weight: 600;
-}
-
-.batch-buttons {
- display: flex;
- gap: 20rpx;
- justify-content: flex-end;
-}
-
-.batch-btn {
- min-width: 180rpx;
-}
-
-// 鍏ㄩ�夋搷浣滃尯鍩熸牱寮�
-.select-all-section {
- display: flex;
- justify-content: space-between;
- align-items: center;
- background: #f8f9fa;
- border-radius: 12rpx;
- padding: 20rpx 24rpx;
- margin-bottom: 20rpx;
- border: 1rpx solid #e9ecef;
-}
-
-.select-all-checkbox {
- display: flex;
- align-items: center;
- gap: 12rpx;
- cursor: pointer;
-}
-
-.select-all-text {
- font-size: 26rpx;
- color: #606266;
- font-weight: 500;
-}
-
-.select-all-hint {
- font-size: 22rpx;
- color: #909399;
-}
-
-// 鍒楄〃椤归�夋嫨鏍峰紡
-.ledger-item {
- display: flex;
- align-items: flex-start;
- padding: 0;
-}
-
-.item-checkbox {
- padding: 24rpx 16rpx 0 24rpx;
- cursor: pointer;
- display: flex;
- align-items: center;
- min-height: 48rpx;
-}
-
-.item-content {
- flex: 1;
- padding: 0;
}
// 鐐瑰嚮缂栬緫鍖哄煙鏍峰紡
diff --git a/src/pages/productionManagement/productionReporting/components/formDia.vue b/src/pages/productionManagement/productionReporting/components/formDia.vue
new file mode 100644
index 0000000..1ef480f
--- /dev/null
+++ b/src/pages/productionManagement/productionReporting/components/formDia.vue
@@ -0,0 +1,160 @@
+<template>
+ <view>
+ <up-popup v-model:show="dialogFormVisible" mode="bottom" round="12" :customStyle="{ height: '70vh' }" @close="closeDia">
+ <view class="dia-container">
+ <view class="dia-header">
+ <text class="title">鐢熶骇鎶ュ伐</text>
+ </view>
+
+ <scroll-view class="rows" scroll-y>
+ <up-form :model="form" label-width="120" ref="formRef">
+ <up-form-item label="鎺掍骇鏁伴噺">
+ <up-input v-model="form.schedulingNum" placeholder="璇疯緭鍏�" disabled />
+ </up-form-item>
+ <up-form-item label="鏈鐢熶骇鏁伴噺" prop="finishedNum">
+ <up-input v-model="form.finishedNum" type="number" placeholder="璇疯緭鍏�" @change="changeNum" />
+ </up-form-item>
+ <up-form-item label="寰呯敓浜ф暟閲�">
+ <up-input v-model="form.pendingNum" placeholder="璇疯緭鍏�" disabled />
+ </up-form-item>
+ <up-form-item label="鐢熶骇浜�" @click="openUserPicker">
+ <up-input v-model="form.schedulingUserName" placeholder="閫夋嫨浜哄憳" readonly @click="openUserPicker" />
+ <template #right>
+ <up-icon name="arrow-right" @click="openUserPicker"></up-icon>
+ </template>
+ </up-form-item>
+ <up-form-item label="鐢熶骇鏃ユ湡" @click="openDatePicker">
+ <up-input v-model="form.schedulingDate" placeholder="璇烽�夋嫨鏃ユ湡" readonly @click="openDatePicker" />
+ <template #right>
+ <up-icon name="calendar" @click="openDatePicker"></up-icon>
+ </template>
+ </up-form-item>
+ </up-form>
+ </scroll-view>
+
+ <view class="dia-footer">
+ <up-button type="primary" @click="submitForm">纭</up-button>
+ <up-button @click="closeDia">鍙栨秷</up-button>
+ </view>
+ </view>
+ </up-popup>
+
+ <!-- 鏃ユ湡閫夋嫨鍣� -->
+ <up-popup :show="datePicker.show" mode="bottom" @close="datePicker.show = false">
+ <up-datetime-picker :show="true" v-model="datePicker.value" mode="date" @confirm="onDateConfirm" @cancel="datePicker.show = false" />
+ </up-popup>
+
+ <!-- 浜哄憳閫夋嫨鍣� -->
+ <up-action-sheet :show="userPicker.show" :actions="userActionList" title="閫夋嫨浜哄憳" @select="onUserSelect" @close="userPicker.show = false" />
+ </view>
+</template>
+
+<script setup>
+import { ref, reactive, toRefs, getCurrentInstance, computed } from "vue";
+import { userListNoPageByTenantId } from "@/api/system/user.js";
+import { productionReport, productionReportUpdate } from "@/api/productionManagement/productionReporting.js";
+const { proxy } = getCurrentInstance()
+
+const emit = defineEmits(['close'])
+
+const userList = ref([])
+const dialogFormVisible = ref(false);
+const operationType = ref('')
+const data = reactive({
+ form: {
+ successNum: "",
+ schedulingNum: "",
+ finishedNum: "",
+ schedulingUserId: "",
+ schedulingUserName: "",
+ schedulingDate: "",
+ },
+ rules: {
+ schedulingNum: [{ required: true, message: "璇疯緭鍏�", trigger: "blur" },],
+ },
+});
+const { form, rules } = toRefs(data);
+
+// pickers
+const datePicker = ref({ show: false, value: Date.now() })
+const userPicker = ref({ show: false })
+const userActionList = computed(() => userList.value.map(u => ({ name: u.nickName, value: u.userId })))
+
+// 鎵撳紑寮规
+const openDialog = (type, row) => {
+ operationType.value = type;
+ dialogFormVisible.value = true;
+ userListNoPageByTenantId().then((res) => {
+ userList.value = res.data;
+ });
+ form.value = { ...row, schedulingUserName: row.schedulingUserName || '' }
+ // 鍒濆鍖栵細鏈鐢熶骇鏁伴噺缃┖锛屽緟鐢熶骇 = 鎺掍骇鏁伴噺
+ const sched = Number(form.value.schedulingNum) || 0
+ form.value.finishedNum = ''
+ form.value.pendingNum = sched
+}
+
+const changeNum = (value) => {
+ const sched = Number(form.value.schedulingNum) || 0
+ let num = Number(value)
+ if (isNaN(num) || num < 0) num = 0
+ if (num > sched) {
+ num = sched
+ uni.showToast({ title: '鏈鐢熶骇鏁伴噺涓嶅彲澶т簬鎺掍骇鏁伴噺', icon: 'none' })
+ }
+ form.value.finishedNum = String(num)
+ form.value.pendingNum = sched - num
+}
+
+const openDatePicker = () => {
+ datePicker.value.value = Date.now()
+ datePicker.value.show = true
+}
+const onDateConfirm = (e) => {
+ const d = new Date(e.value)
+ const y = d.getFullYear(); const m = String(d.getMonth()+1).padStart(2, '0'); const day = String(d.getDate()).padStart(2, '0')
+ form.value.schedulingDate = `${y}-${m}-${day}`
+ datePicker.value.show = false
+}
+const openUserPicker = () => { userPicker.value.show = true }
+const onUserSelect = (item) => {
+ if (item) {
+ form.value.schedulingUserId = item.value
+ form.value.schedulingUserName = item.name
+ }
+ userPicker.value.show = false
+}
+
+// 鎻愪氦浜у搧琛ㄥ崟
+const submitForm = () => {
+ // 绠�鍖栨牎楠岋細浠呯‘淇濆繀濉瓧娈�
+ if (!form.value.finishedNum || !form.value.schedulingUserId || !form.value.schedulingDate) {
+ uni.showToast({ title: '璇峰畬鍠勫繀濉俊鎭�', icon: 'none' })
+ return
+ }
+ form.value.staffState = 1
+ const req = operationType.value === 'add' ? productionReport : productionReportUpdate
+ req(form.value).then(() => {
+ uni.showToast({ title: '鎻愪氦鎴愬姛', icon: 'success' })
+ closeDia()
+ })
+}
+
+// 鍏抽棴寮规
+const closeDia = () => {
+ dialogFormVisible.value = false;
+ emit('close')
+};
+
+defineExpose({
+ openDialog,
+});
+</script>
+
+<style scoped lang="scss">
+.dia-container { padding: 12px; height: 100%; display: flex; flex-direction: column; }
+.dia-header { display: flex; align-items: center; justify-content: center; margin-bottom: 10px; }
+.title { font-weight: 600; font-size: 16px; color: #333; }
+.rows { flex: 1; min-height: 0; overflow-y: auto; }
+.dia-footer { display: flex; gap: 10px; justify-content: flex-end; padding-top: 8px; }
+</style>
\ No newline at end of file
diff --git a/src/pages/productionManagement/productionReporting/index.vue b/src/pages/productionManagement/productionReporting/index.vue
new file mode 100644
index 0000000..b8b92f4
--- /dev/null
+++ b/src/pages/productionManagement/productionReporting/index.vue
@@ -0,0 +1,202 @@
+<template>
+ <view class="prod-reporting">
+ <PageHeader title="鐢熶骇鎶ュ伐" />
+
+ <view class="search_form">
+ <u-form>
+ <view class="form-row">
+ <u-form-item label="瀹㈡埛鍚嶇О" label-width="80">
+ <up-input v-model="searchForm.customerName" placeholder="璇疯緭鍏�" clearable @change="handleQuery" />
+ </u-form-item>
+ <u-form-item label="椤圭洰鍚嶇О" label-width="80">
+ <up-input v-model="searchForm.projectName" placeholder="璇疯緭鍏�" clearable @change="handleQuery" />
+ </u-form-item>
+ </view>
+ <view class="form-row">
+ <u-form-item label="鐘舵��" label-width="80">
+ <up-input v-model="statusDisplay" placeholder="璇烽�夋嫨鐘舵��" readonly @click="showStatusPicker = true" />
+ </u-form-item>
+ </view>
+ <view class="form-actions">
+ <u-button type="primary" size="small" @click="handleQuery">鎼滅储</u-button>
+ </view>
+ </u-form>
+ </view>
+
+ <view class="list_container">
+ <u-loading-icon v-if="tableLoading" text="鍔犺浇涓�..." />
+ <view v-else>
+ <view v-if="!tableData || tableData.length === 0" class="empty">鏆傛棤鏁版嵁</view>
+ <view v-else class="card_list">
+ <view v-for="item in tableData" :key="item.id" class="card_item">
+ <view class="card_header">
+ <u-tag :type="statusType(item.status)" size="mini">{{ statusText(item.status) }}</u-tag>
+ <text class="card_title">{{ item.projectName }}</text>
+ </view>
+ <view class="card_body">
+ <view class="row"><text class="label">鎺掍骇鏃ユ湡</text><text class="value">{{ item.schedulingDate }}</text></view>
+ <view class="row"><text class="label">鎺掍骇浜�</text><text class="value">{{ item.schedulingUserName }}</text></view>
+ <view class="row"><text class="label">鍚堝悓鍙�</text><text class="value">{{ item.salesContractNo }}</text></view>
+ <view class="row"><text class="label">瀹㈡埛鍚堝悓鍙�</text><text class="value">{{ item.customerContractNo }}</text></view>
+ <view class="row"><text class="label">瀹㈡埛鍚嶇О</text><text class="value">{{ item.customerName }}</text></view>
+ <view class="row"><text class="label">浜у搧澶х被</text><text class="value">{{ item.productCategory }}</text></view>
+ <view class="row"><text class="label">瑙勬牸鍨嬪彿</text><text class="value">{{ item.specificationModel }}</text></view>
+ <view class="row inline">
+ <view class="col"><text class="label">鍗曚綅</text><text class="value">{{ item.unit }}</text></view>
+ <view class="col"><text class="label">鎺掍骇鏁伴噺</text><text class="value">{{ item.schedulingNum }}</text></view>
+ <view class="col"><text class="label">鐢熶骇鏁伴噺</text><text class="value">{{ item.finishedNum }}</text></view>
+ <view class="col"><text class="label">寰呯敓浜ф暟閲�</text><text class="value">{{ item.pendingFinishNum }}</text></view>
+ </view>
+ </view>
+ <view class="card_actions">
+ <u-button type="primary" size="small" @click="openForm('add', item)" :disabled="item.pendingFinishNum == 0">鐢熶骇鎶ュ伐</u-button>
+ </view>
+ </view>
+ </view>
+
+
+ </view>
+ </view>
+
+ <form-dia ref="formDia" @close="handleQuery"></form-dia>
+
+ <!-- 鐘舵�侀�夋嫨鍣� -->
+ <up-action-sheet :show="showStatusPicker" :actions="statusActions" title="閫夋嫨鐘舵��" @select="onStatusSelect" @close="showStatusPicker = false" />
+ </view>
+</template>
+
+<script setup>
+import { onMounted, ref, reactive, toRefs, nextTick, computed } from "vue";
+import PageHeader from '@/components/PageHeader.vue'
+import FormDia from './components/formDia.vue'
+import { workListPage } from "@/api/productionManagement/productionReporting.js";
+const data = reactive({
+ searchForm: {
+ customerName: "",
+ projectName: "",
+ status: undefined,
+ },
+});
+const { searchForm } = toRefs(data);
+const showStatusPicker = ref(false)
+const statusOptions = ref([
+ { label: '寰呯敓浜�', value: 1 },
+ { label: '鐢熶骇涓�', value: 2 },
+ { label: '宸叉姤宸�', value: 3 },
+])
+const statusActions = computed(() => statusOptions.value.map(o => ({ name: o.label, value: o.value })))
+const statusDisplay = ref('')
+const tableData = ref([]);
+const tableLoading = ref(false);
+const page = reactive({
+ current: -1,
+ size: -1,
+});
+const formDia = ref()
+
+// 鏌ヨ鍒楄〃
+/** 鎼滅储鎸夐挳鎿嶄綔 */
+const handleQuery = () => {
+ page.current = -1;
+ page.size = -1;
+ getList();
+};
+const getList = () => {
+ tableLoading.value = true;
+ const params = { ...searchForm.value, ...page };
+ workListPage(params).then(res => {
+ tableLoading.value = false;
+ tableData.value = res.data.records.map(item => ({
+ ...item,
+ pendingFinishNum: (Number(item.schedulingNum) || 0) - (Number(item.finishedNum) || 0)
+ }));
+ }).catch(err => {
+ tableLoading.value = false;
+ })
+};
+// 鐘舵�侀�夋嫨
+const onStatusSelect = (item) => {
+ searchForm.value.status = item?.value
+ statusDisplay.value = item?.name || ''
+ showStatusPicker.value = false
+ handleQuery()
+}
+// 鎵撳紑寮规
+const openForm = (type, row) => {
+ if (!row) return
+ if ((Number(row.pendingFinishNum) || 0) === 0) {
+ uni.showToast({ title: '鏃犻渶鍐嶆姤宸�', icon: 'none' })
+ return
+ }
+ nextTick(() => { formDia.value?.openDialog(type, row) })
+};
+
+// 鐘舵�佹枃鏈�/绫诲瀷
+const statusText = (s) => {
+ if (s == 3) return '宸叉姤宸�'
+ if (s == 1) return '寰呯敓浜�'
+ return '鐢熶骇涓�'
+}
+const statusType = (s) => {
+ if (s == 3) return 'success'
+ if (s == 1) return 'primary'
+ return 'warning'
+}
+
+// 鏃犲垎椤�
+
+// 鏄庣粏浜哄憳/鏃ユ湡閫夋嫨
+const openChildUserPicker = (index) => {
+ if (!expandData.value[index]?.editType) return
+ childUserPicker.value.index = index
+ childUserPicker.value.show = true
+}
+const onChildUserSelect = (item) => {
+ if (item && childUserPicker.value.index > -1) {
+ const row = expandData.value[childUserPicker.value.index]
+ row.schedulingUserId = item.value
+ row.schedulingUserName = item.name
+ }
+ childUserPicker.value.show = false
+}
+const openChildDatePicker = (index) => {
+ if (!expandData.value[index]?.editType) return
+ childDatePicker.value.index = index
+ childDatePicker.value.value = Date.now()
+ childDatePicker.value.show = true
+}
+const onChildDateConfirm = (e) => {
+ const d = new Date(e.value)
+ const y = d.getFullYear(); const m = String(d.getMonth()+1).padStart(2, '0'); const day = String(d.getDate()).padStart(2, '0')
+ const str = `${y}-${m}-${day}`
+ if (childDatePicker.value.index > -1) {
+ expandData.value[childDatePicker.value.index].schedulingDate = str
+ }
+ childDatePicker.value.show = false
+}
+
+onMounted(() => {
+ getList();
+});
+</script>
+
+<style scoped lang="scss">
+.prod-reporting { padding-bottom: 12px; }
+.search_form { margin: 12px; background: #fff; border-radius: 8px; padding: 10px; }
+.form-row { display: flex; gap: 12px; }
+.form-actions { display: flex; justify-content: flex-end; }
+.list_container { padding: 0 12px; }
+.empty { text-align: center; color: #888; padding: 20px 0; }
+.card_list { display: flex; flex-direction: column; gap: 10px; }
+.card_item { background: #fff; border-radius: 10px; padding: 10px; box-shadow: 0 2px 8px rgba(0,0,0,0.05); }
+.card_header { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; }
+.card_title { font-weight: 500; color: #333; margin-left: auto; }
+.card_body .row { display: flex; justify-content: space-between; padding: 4px 0; }
+.card_body .row.inline { display: flex; gap: 10px; flex-wrap: wrap; }
+.card_body .row.inline .col { min-width: 45%; display: flex; justify-content: space-between; }
+.label { color: #666; font-size: 12px; }
+.value { color: #333; font-size: 12px; }
+.card_actions { display: flex; justify-content: flex-end; gap: 8px; padding-top: 8px; }
+.ml8 { margin-left: 8px; }
+
+</style>
diff --git a/src/pages/sales/salesAccount/detail.vue b/src/pages/sales/salesAccount/detail.vue
index 62bff3d..27d9f43 100644
--- a/src/pages/sales/salesAccount/detail.vue
+++ b/src/pages/sales/salesAccount/detail.vue
@@ -223,6 +223,18 @@
></up-icon>
</template>
</up-form-item>
+ <!-- 缁戝畾鏈哄櫒 -->
+ <up-form-item
+ label="缁戝畾鏈哄櫒"
+ prop="speculativeTradingName"
+ required
+ >
+ <up-input
+ disabled
+ v-model="product.speculativeTradingName"
+ placeholder="璇疯緭鍏�"
+ />
+ </up-form-item>
<!-- 鍗曚綅 -->
<up-form-item
@@ -437,7 +449,8 @@
return modelOptions.value.map(model => ({
name: model.text,
value: model.value,
- unit: model.unit
+ unit: model.unit,
+ speculativeTradingName: model.speculativeTradingName
}))
})
@@ -521,6 +534,7 @@
specificationModel: '',
productModelId: '',
unit: '',
+ speculativeTradingName: '',
taxRate: '',
taxInclusiveUnitPrice: '',
quantity: '',
@@ -603,14 +617,17 @@
text: user.model,
value: user.id,
unit: user.unit,
+ speculativeTradingName: user.speculativeTradingName,
}));
});
};
// 瑙勬牸鍨嬪彿閫夋嫨浜嬩欢
const onSpecificationSelect = (item) => {
+console.log('selected item---', item);
productData.value[currentProductIndex.value].specificationModel = item.name
productData.value[currentProductIndex.value].productModelId = item.value
productData.value[currentProductIndex.value].unit = item.unit
+ productData.value[currentProductIndex.value].speculativeTradingName = item.speculativeTradingName
}
// 绋庣巼閫夋嫨浜嬩欢
const onTaxRateSelect = (item) => {
diff --git a/src/pages/sales/salesAccount/index.vue b/src/pages/sales/salesAccount/index.vue
index 83ed3dd..acc8783 100644
--- a/src/pages/sales/salesAccount/index.vue
+++ b/src/pages/sales/salesAccount/index.vue
@@ -22,7 +22,7 @@
</view>
<!-- 閿�鍞彴璐︾�戝竷娴� -->
- <view class="ledger-list" v-if="total > 0">
+ <view class="ledger-list" v-if="ledgerList.length > 0">
<view v-for="(item, index) in ledgerList" :key="index">
<view class="ledger-item" @click="handleInfo('edit', item)">
<view class="item-header">
@@ -115,7 +115,6 @@
// 閿�鍞彴璐︽暟鎹�
const ledgerList = ref([]);
-const total = ref(0);
// 杩斿洖涓婁竴椤�
const goBack = () => {
@@ -129,8 +128,8 @@
size: -1
}
ledgerListPage({...page, salesContractNo: salesContractNo.value}).then((res) => {
+ console.log('閿�鍞彴璐�----', res);
ledgerList.value = res.records;
- total.value = res.total;
closeToast()
}).catch(() => {
closeToast()
diff --git a/src/pages/sales/salesAccount/view.vue b/src/pages/sales/salesAccount/view.vue
index acfb6e0..326fb32 100644
--- a/src/pages/sales/salesAccount/view.vue
+++ b/src/pages/sales/salesAccount/view.vue
@@ -69,6 +69,10 @@
<text class="info-value">{{ product.specificationModel }}</text>
</view>
<view class="info-item">
+ <text class="info-label">缁戝畾鏈哄櫒</text>
+ <text class="info-value">{{ product.speculativeTradingName }}</text>
+ </view>
+ <view class="info-item">
<text class="info-label">鍗曚綅</text>
<text class="info-value">{{ product.unit }}</text>
</view>
diff --git a/src/utils/request.ts b/src/utils/request.ts
index 23752c2..7e40c06 100644
--- a/src/utils/request.ts
+++ b/src/utils/request.ts
@@ -22,12 +22,12 @@
config.url = url
}
// 璁板綍璇锋眰鍙傛暟
- console.log('璇锋眰鍙戦�佸弬鏁�:', {
- url: (config.baseUrl || baseUrl) + config.url,
- method: config.method || 'GET',
- headers: config.header,
- data: config.data
- })
+ // console.log('璇锋眰鍙戦�佸弬鏁�:', {
+ // url: (config.baseUrl || baseUrl) + config.url,
+ // method: config.method || 'GET',
+ // headers: config.header,
+ // data: config.data
+ // })
return new Promise((resolve, reject) => {
uni.request({
method: config.method || 'GET',
@@ -49,11 +49,11 @@
// @ts-ignore
const msg: string = errorCode[code] || data.msg || errorCode['default']
// 璁板綍鎺ユ敹鍒扮殑鍙傛暟
- console.log('鎺ユ敹鍒扮殑鍙傛暟:', {
- url: (config.baseUrl || baseUrl) + config.url,
- code: code,
- data: data
- })
+ // console.log('鎺ユ敹鍒扮殑鍙傛暟:', {
+ // url: (config.baseUrl || baseUrl) + config.url,
+ // code: code,
+ // data: data
+ // })
if (code === 401) {
showConfirm('鐧诲綍鐘舵�佸凡杩囨湡锛屾偍鍙互缁х画鐣欏湪璇ラ〉闈紝鎴栬�呴噸鏂扮櫥褰�?').then(res => {
if (res.confirm) {
--
Gitblit v1.9.3