feat: 上下模页面新增操作人选择与备注功能

master
zhongwenkai 3 days ago
parent e1d1fb3e52
commit 36d41897b3

@ -338,6 +338,7 @@ export default {
validatorOperateTypeRequired: 'Operation type is required',
validatorDeviceRequired: 'Device is required',
validatorMoldRequired: 'Select at least one mold for mounting',
validatorOperatorRequired: 'Please select operator',
validatorLowerMoldRequired: 'Select at least one mold for dismounting',
loadEditFailed: 'Failed to load edit data',
confirmDelete: 'Confirm delete this mold operate record?',
@ -372,6 +373,8 @@ export default {
historySuffix: ' History',
historyTitle: 'Mold Operate History',
searchPlaceholder: 'Search device/mold name',
operator: 'Operator',
placeholderOperator: 'Select operator',
filterAll: 'All',
filterToday: 'Today',
filterWeek: 'This Week',

@ -338,6 +338,7 @@ export default {
validatorOperateTypeRequired: '操作类型不能为空',
validatorDeviceRequired: '设备不能为空',
validatorMoldRequired: '请至少选择一个上模模具',
validatorOperatorRequired: '请选择操作人',
validatorLowerMoldRequired: '请至少选择一个下模模具',
loadEditFailed: '加载编辑数据失败',
confirmDelete: '确认删除该上下模记录吗?',
@ -372,6 +373,8 @@ export default {
historySuffix: '历史',
historyTitle: '上下模历史',
searchPlaceholder: '搜索设备/模具名称',
operator: '操作人',
placeholderOperator: '请选择操作人',
filterAll: '全部',
filterToday: '今天',
filterWeek: '本周',

@ -113,6 +113,60 @@
</view>
</view>
<!-- 操作人与备注 -->
<view class="section-card">
<view class="section-title-bar">
<view class="section-bar-line"></view>
<text class="section-title">{{ t('moldOperate.operator') + ' & ' + t('moldOperate.remark') }}</text>
</view>
<view class="card-body-grid">
<view class="form-row">
<view class="form-cell">
<text class="form-label"><text class="required">*</text>{{ t('moldOperate.operator') }}</text>
<view class="select-dropdown" @click="toggleOperatorDropdown">
<view class="dropdown-input">
<text :class="['dropdown-value', { placeholder: !selectedOperator }]">
{{ selectedOperator ? selectedOperator.label : t('moldOperate.placeholderOperator') }}
</text>
<uni-icons type="bottom" size="14" color="#999" class="dropdown-arrow"></uni-icons>
</view>
<view v-if="showOperatorDropdown" class="dropdown-panel">
<scroll-view scroll-y class="dropdown-scroll">
<view
v-for="(item, idx) in operatorOptions"
:key="item.value"
class="dropdown-item"
:class="{ active: selectedOperator?.value === item.value }"
@click.stop="handleSelectOperator(item, idx)"
>
<text class="dropdown-item-text">{{ item.label }}</text>
<uni-icons
v-if="selectedOperator?.value === item.value"
type="checkmarkempty"
size="16"
color="#1f7cff"
></uni-icons>
</view>
<view v-if="!operatorOptions.length" class="dropdown-empty"></view>
</scroll-view>
</view>
</view>
</view>
</view>
<view class="form-row">
<view class="form-cell">
<text class="form-label">{{ t('moldOperate.remark') }}</text>
<input
class="form-input"
v-model="remarkText"
:placeholder="t('moldOperate.placeholderRemark')"
:maxlength="200"
/>
</view>
</view>
</view>
</view>
<!-- 底部操作栏 -->
<view class="bottom-actions">
<view class="bottom-btn confirm-btn" @click="handleConfirm">{{ t('moldOperate.confirmDismount') }}</view>
@ -150,6 +204,7 @@ import { onShow } from '@dcloudio/uni-app'
import { useI18n } from 'vue-i18n'
import { getLowerMoldList, getDeviceLedgerList, createMoldOperate } from '@/api/mes/moldoperate'
import { getDeviceLineTree } from '@/api/mes/deviceLine'
import { getSimpleUserList } from '@/api/system/user'
const { t } = useI18n()
@ -225,6 +280,13 @@ const lowerMoldLoading = ref(false)
const moldsLoaded = ref(false)
const tempSelectedMoldId = ref(null)
// ---- ----
const remarkText = ref('')
const operatorOptions = ref([])
const selectedOperator = ref(null)
const operatorIndex = ref(-1)
const showOperatorDropdown = ref(false)
// ---- ----
function textValue(v) {
if (v === 0) return '0'
@ -442,6 +504,34 @@ function confirmLowerMoldSelection() {
function closeLowerMoldPicker() { lowerMoldPickerRef.value?.close() }
// ---- ----
async function loadOperators() {
try {
const res = await getSimpleUserList()
const data = Array.isArray(res) ? res : (Array.isArray(res?.data) ? res.data : [])
operatorOptions.value = data.map((u) => ({
value: u.id || u.userId,
label: u.nickname || u.userName || u.name || String(u.id || '')
}))
} catch (e) {
console.error('loadOperators error', e)
}
}
function toggleOperatorDropdown() {
showOperatorDropdown.value = !showOperatorDropdown.value
}
function closeOperatorDropdown() {
showOperatorDropdown.value = false
}
function handleSelectOperator(item, idx) {
selectedOperator.value = item
operatorIndex.value = idx
closeOperatorDropdown()
}
function goToHistory() {
uni.navigateTo({ url: '/pages_function/pages/moldoperate/history?type=down' })
}
@ -460,6 +550,10 @@ function validForm() {
uni.showToast({ title: t('moldOperate.validatorLowerMoldRequired'), icon: 'none' })
return false
}
if (!selectedOperator.value) {
uni.showToast({ title: t('moldOperate.validatorOperatorRequired'), icon: 'none' })
return false
}
return true
}
@ -474,6 +568,13 @@ async function handleConfirm() {
lineId: device.deviceLine != null ? String(device.deviceLine) : undefined,
lineName: device.deviceLine != null ? getTopLineName(device.deviceLine) : (device.lineName || undefined)
}
//
if (selectedOperator.value) {
payload.operatorId = selectedOperator.value.value
}
if (remarkText.value.trim()) {
payload.remark = remarkText.value.trim()
}
console.log('[下模] 提交参数 =', JSON.stringify(payload))
await createMoldOperate(payload)
uni.showToast({ title: t('functionCommon.createSuccess'), icon: 'success' })
@ -489,6 +590,9 @@ async function handleConfirm() {
selectedDevice.value = {}
selectedMold.value = {}
lowerMoldOptions.value = []
remarkText.value = ''
selectedOperator.value = null
operatorIndex.value = -1
} catch (e) {
console.error('[下模] 保存失败 =', e)
const errMsg = e?.msg || (typeof e === 'string' ? e : e?.message) || '系统异常'
@ -497,7 +601,7 @@ async function handleConfirm() {
}
onShow(async () => {
await Promise.allSettled([loadDevices(), loadLineTree()])
await Promise.allSettled([loadDevices(), loadLineTree(), loadOperators()])
// globalData
const device = getApp().globalData._deviceSelectResult
if (device) {
@ -606,7 +710,6 @@ onShow(async () => {
margin: 16rpx 24rpx;
background: #ffffff;
border-radius: 14rpx;
overflow: hidden;
}
.section-title-bar {
@ -735,6 +838,130 @@ onShow(async () => {
}
}
/* ====== 表单行(操作人/备注) ====== */
.form-row {
margin-top: 18rpx;
&:first-child {
margin-top: 0;
}
}
.form-cell {
width: 100%;
.form-label {
display: block;
font-size: 24rpx;
color: #999;
margin-bottom: 8rpx;
.required {
color: #dc2626;
margin-right: 4rpx;
}
}
}
/* ====== 下拉选择器(操作人) ====== */
.select-dropdown {
position: relative;
width: 100%;
}
.dropdown-input {
display: flex;
align-items: center;
height: 72rpx;
padding: 0 20rpx;
border: 1rpx solid #e0e0e0;
border-radius: 10rpx;
font-size: 27rpx;
color: #333;
background: #f9fafb;
}
.dropdown-value {
flex: 1;
&.placeholder {
color: #bbb;
}
}
.dropdown-arrow {
position: absolute;
right: 16rpx;
top: 50%;
transform: translateY(-50%);
pointer-events: none;
}
/* ====== 下拉面板(在输入框下方展开) ====== */
.dropdown-panel {
position: absolute;
top: 76rpx;
left: 0;
right: 0;
z-index: 99;
background: #ffffff;
border: 1rpx solid #e0e0e0;
border-radius: 12rpx;
box-shadow: 0 8rpx 30rpx rgba(0, 0, 0, 0.1);
overflow: hidden;
}
.dropdown-scroll {
max-height: 400rpx;
}
.dropdown-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 22rpx 28rpx;
font-size: 27rpx;
color: #333;
border-bottom: 1rpx solid #f5f5f5;
&:last-child {
border-bottom: none;
}
&:active {
background: #f5f7fa;
}
&.active {
color: #1f7cff;
background: #f0f7ff;
}
}
.dropdown-item-text {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.dropdown-empty {
padding: 32rpx;
text-align: center;
font-size: 26rpx;
color: #999;
}
.form-input {
height: 72rpx;
padding: 0 20rpx;
border: 1rpx solid #e0e0e0;
border-radius: 10rpx;
font-size: 27rpx;
color: #333;
background: #f9fafb;
}
/* ====== 空提示 ====== */
.empty-mold-hint {
padding: 40rpx 24rpx;

@ -107,6 +107,62 @@
</view>
</view>
<!-- 操作人与备注 -->
<view class="section-card">
<view class="section-title-bar">
<view class="section-bar-line"></view>
<text class="section-title">{{ t('moldOperate.operator') + ' & ' + t('moldOperate.remark') }}</text>
</view>
<view class="card-body-grid">
<view class="form-row">
<view class="form-cell">
<text class="form-label"><text class="required">*</text>{{ t('moldOperate.operator') }}</text>
<!-- 下拉选择器 -->
<view class="select-dropdown" @click="toggleOperatorDropdown">
<view class="dropdown-input">
<text :class="['dropdown-value', { placeholder: !selectedOperator }]">
{{ selectedOperator ? selectedOperator.label : t('moldOperate.placeholderOperator') }}
</text>
<uni-icons type="bottom" size="14" color="#999" class="dropdown-arrow"></uni-icons>
</view>
<!-- 下拉列表在输入框下方展开 -->
<view v-if="showOperatorDropdown" class="dropdown-panel">
<scroll-view scroll-y class="dropdown-scroll">
<view
v-for="(item, idx) in operatorOptions"
:key="item.value"
class="dropdown-item"
:class="{ active: selectedOperator?.value === item.value }"
@click.stop="handleSelectOperator(item, idx)"
>
<text class="dropdown-item-text">{{ item.label }}</text>
<uni-icons
v-if="selectedOperator?.value === item.value"
type="checkmarkempty"
size="16"
color="#1f7cff"
></uni-icons>
</view>
<view v-if="!operatorOptions.length" class="dropdown-empty"></view>
</scroll-view>
</view>
</view>
</view>
</view>
<view class="form-row">
<view class="form-cell">
<text class="form-label">{{ t('moldOperate.remark') }}</text>
<input
class="form-input"
v-model="remarkText"
:placeholder="t('moldOperate.placeholderRemark')"
:maxlength="200"
/>
</view>
</view>
</view>
</view>
<!-- 底部操作栏 - 上模 -->
<view class="bottom-actions">
<view class="bottom-btn cancel-btn" @click="handleCancel">{{ t('functionCommon.cancel') }}</view>
@ -121,6 +177,7 @@ import { computed, ref } from 'vue'
import { onShow } from '@dcloudio/uni-app'
import { useI18n } from 'vue-i18n'
import { getDeviceLedgerList, createMoldOperate } from '@/api/mes/moldoperate'
import { getSimpleUserList } from '@/api/system/user'
const { t } = useI18n()
@ -160,6 +217,10 @@ const selectedDevice = ref({})
const selectedMountMolds = ref([])
const tempSelectedDeviceId = ref(null)
const remarkText = ref('')
const operatorOptions = ref([])
const selectedOperator = ref(null)
const operatorIndex = ref(-1)
// -
const deviceStatusClass = computed(() => {
@ -241,6 +302,36 @@ const currentMoldDisplay = computed(() => {
return textValue(selectedDevice.value.currentMold)
})
// ---- ----
async function loadOperators() {
try {
const res = await getSimpleUserList()
const data = Array.isArray(res) ? res : (Array.isArray(res?.data) ? res.data : [])
operatorOptions.value = data.map((u) => ({
value: u.id || u.userId,
label: u.nickname || u.userName || u.name || String(u.id || '')
}))
} catch (e) {
console.error('loadOperators error', e)
}
}
const showOperatorDropdown = ref(false)
function toggleOperatorDropdown() {
showOperatorDropdown.value = !showOperatorDropdown.value
}
function closeOperatorDropdown() {
showOperatorDropdown.value = false
}
function handleSelectOperator(item, idx) {
selectedOperator.value = item
operatorIndex.value = idx
closeOperatorDropdown()
}
function selectDevice(device) {
selectedDevice.value = device || {}
tempSelectedDeviceId.value = device ? device.id : null
@ -272,6 +363,10 @@ function validFormMount() {
uni.showToast({ title: t('moldOperate.validatorMoldRequired'), icon: 'none' })
return false
}
if (!selectedOperator.value) {
uni.showToast({ title: t('moldOperate.validatorOperatorRequired'), icon: 'none' })
return false
}
return true
}
@ -286,6 +381,13 @@ async function handleConfirmMount() {
lineName: selectedDevice.value.workshopName || '',
lineId: selectedDevice.value.deviceLine || ''
}
//
if (selectedOperator.value) {
payload.operatorId = selectedOperator.value.value
}
if (remarkText.value.trim()) {
payload.remark = remarkText.value.trim()
}
console.log('=== 上模提交参数 ===', JSON.stringify(payload))
console.log('=== deviceId type:', typeof payload.deviceId, 'value:', payload.deviceId)
console.log('=== moldId type:', typeof payload.moldId, 'value:', payload.moldId)
@ -347,6 +449,9 @@ function handleScan() {
function handleCancel() {
selectedDevice.value = {}
selectedMountMolds.value = []
remarkText.value = ''
selectedOperator.value = null
operatorIndex.value = -1
}
function goBack() {
@ -358,7 +463,7 @@ function goToHistory() {
}
onShow(async () => {
await Promise.allSettled([loadDevices()])
await Promise.allSettled([loadDevices(), loadOperators()])
// globalData
const device = getApp().globalData._deviceSelectResult
if (device) {
@ -473,7 +578,6 @@ onShow(async () => {
margin: 16rpx 24rpx;
background: #ffffff;
border-radius: 14rpx;
overflow: hidden;
}
.section-title-bar {
@ -592,6 +696,132 @@ onShow(async () => {
.change-text { font-size: 26rpx; color: #666; }
}
/* ====== 表单行(操作人/备注) ====== */
.form-row {
margin-top: 18rpx;
&:first-child {
margin-top: 0;
}
}
.form-cell {
width: 100%;
.form-label {
display: block;
font-size: 24rpx;
color: #999;
margin-bottom: 8rpx;
.required {
color: #dc2626;
margin-right: 4rpx;
}
}
}
/* ====== 下拉选择器(操作人) ====== */
.select-dropdown {
position: relative;
width: 100%;
}
.dropdown-input {
display: flex;
align-items: center;
height: 72rpx;
padding: 0 20rpx;
border: 1rpx solid #e0e0e0;
border-radius: 10rpx;
font-size: 27rpx;
color: #333;
background: #f9fafb;
}
.dropdown-value {
flex: 1;
&.placeholder {
color: #bbb;
}
}
.dropdown-arrow {
position: absolute;
right: 16rpx;
top: 50%;
transform: translateY(-50%);
pointer-events: none;
}
/* ====== 下拉面板(在输入框下方展开) ====== */
.dropdown-panel {
position: absolute;
top: 76rpx;
left: 0;
right: 0;
z-index: 99;
background: #ffffff;
border: 1rpx solid #e0e0e0;
border-radius: 12rpx;
box-shadow: 0 8rpx 30rpx rgba(0, 0, 0, 0.1);
overflow: hidden;
}
.dropdown-scroll {
max-height: 400rpx;
}
.dropdown-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 22rpx 28rpx;
font-size: 27rpx;
color: #333;
border-bottom: 1rpx solid #f5f5f5;
&:last-child {
border-bottom: none;
}
&:active {
background: #f5f7fa;
}
&.active {
color: #1f7cff;
background: #f0f7ff;
}
}
.dropdown-item-text {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.dropdown-empty {
padding: 32rpx;
text-align: center;
font-size: 26rpx;
color: #999;
}
.form-input {
height: 72rpx;
padding: 0 20rpx;
border: 1rpx solid #e0e0e0;
border-radius: 10rpx;
font-size: 27rpx;
color: #333;
background: #f9fafb;
}
/* ====== 空提示 ====== */
.empty-mold-hint {
padding: 40rpx 24rpx;

Loading…
Cancel
Save