style: 将模具维修页面的维修对象中模具选择修改为可以用红外扫描的方式

master
zhongwenkai 2 days ago
parent a52277bca2
commit d899357d4b

@ -1352,7 +1352,8 @@ export default {
autoCode: 'Auto Code',
moldLabel: 'Mold',
reportTimeLabel: 'Report Time',
searchPlaceholder: 'Enter code or mold code',
searchPlaceholder: 'Enter code or mold code',
scanMoldPlaceholder: 'Scan/enter mold QR code',
empty: 'No repair records',
statusPending: 'Pending',
statusPassed: 'Passed',

@ -1353,7 +1353,8 @@ export default {
autoCode: '自动生成',
moldLabel: '模具',
reportTimeLabel: '报修时间',
searchPlaceholder: '请输入单号或模具编码',
searchPlaceholder: '请输入单号或模具编码',
scanMoldPlaceholder: '请扫描/输入模具二维码编码',
empty: '暂无维修记录',
statusPending: '待维修',
statusPassed: '通过',
@ -1366,7 +1367,7 @@ export default {
placeholderAcceptedBy: '请选择维修人员',
placeholderConfirmBy: '请选择验收人员',
placeholderUserSearch: '请输入姓名搜索',
placeholderMold: '选择模具',
placeholderMold: '选择模具',
placeholderMoldNameAuto: '自动带出',
placeholderMoldCodeAuto: '自动带出',
placeholderFaultPhenomenon: '请输入故障现象',

@ -16,7 +16,7 @@
:placeholder="t('moldRepair.placeholderRepairCode')"
:disabled="repairCodeDisabled"
/>
<view v-if="mode === 'create'" class="auto-code-wrap" @click="toggleAutoCode">
<view v-if="mode === 'create'" class="auto-code-wrap">
<text class="auto-code-text">{{ t('moldRepair.autoCode') }}</text>
<switch :checked="Boolean(formData.isCode)" color="#1f4b79" @change="toggleAutoCode" />
</view>
@ -54,8 +54,9 @@
<view class="form-item inline-radio">
<text class="form-label required">{{ t('moldRepair.faultLevel') }}</text>
<radio-group class="radio-group" :disabled="readonlyMeta" @change="onFaultLevelChange">
<label v-for="item in faultLevelOptions" :key="String(item.value)" class="radio-item">
<radio :checked="String(formData.faultLevel || '') === String(item.value)" :disabled="readonlyMeta" :value="String(item.value)" color="#1f4b79" />
<label v-for="item in faultLevelOptions" :key="String(item.value)" class="radio-item" @click="selectFaultLevel(String(item.value))">
<radio class="native-radio" :checked="String(formData.faultLevel || '') === String(item.value)" :disabled="readonlyMeta" :value="String(item.value)" />
<view :class="['radio-dot', String(formData.faultLevel || '') === String(item.value) ? 'checked' : '', readonlyMeta ? 'disabled' : '']"></view>
<text>{{ item.label }}</text>
</label>
</radio-group>
@ -64,12 +65,14 @@
<view class="form-item inline-radio">
<text class="form-label required">{{ t('moldRepair.isShutdown') }}</text>
<radio-group class="radio-group" :disabled="readonlyMeta" @change="onShutdownChange">
<label class="radio-item">
<radio :checked="formData.isShutdown === true" :disabled="readonlyMeta" value="true" color="#1f4b79" />
<label class="radio-item" @click="selectShutdown(true)">
<radio class="native-radio" :checked="formData.isShutdown === true" :disabled="readonlyMeta" value="true" />
<view :class="['radio-dot', formData.isShutdown === true ? 'checked' : '', readonlyMeta ? 'disabled' : '']"></view>
<text>{{ t('functionCommon.yes') }}</text>
</label>
<label class="radio-item">
<radio :checked="formData.isShutdown === false" :disabled="readonlyMeta" value="false" color="#1f4b79" />
<label class="radio-item" @click="selectShutdown(false)">
<radio class="native-radio" :checked="formData.isShutdown === false" :disabled="readonlyMeta" value="false" />
<view :class="['radio-dot', formData.isShutdown === false ? 'checked' : '', readonlyMeta ? 'disabled' : '']"></view>
<text>{{ t('functionCommon.no') }}</text>
</label>
</radio-group>
@ -85,15 +88,24 @@
<view class="section-title">{{ t('moldRepair.repairObject') }}</view>
<view class="form-item">
<view class="form-label-row">
<text class="form-label form-label-inline required">{{ t('moldRepair.mold') }}</text>
<view v-if="!readonlyBase" class="field-scan-btn" @click="handleMoldScan">
<u-icon name="scan" size="18" color="#1f4b79"></u-icon>
</view>
<text class="form-label required">{{ t('moldRepair.mold') }}</text>
<view class="mold-search-row">
<input
id="mold-repair-scan-input"
v-model="moldCodeSearch"
class="mold-scan-input"
type="text"
:placeholder="t('moldRepair.scanMoldPlaceholder')"
:disabled="readonlyBase"
@confirm="onMoldCodeSearch"
/>
<picker :range="moldLabels" :value="moldIndex" :disabled="readonlyBase" @change="onMoldChange">
<view :class="['mold-picker', !formData.moldId ? 'is-placeholder' : '']">
<text class="picker-field-text">{{ selectedMoldLabel }}</text>
<uni-icons v-if="!readonlyBase" type="bottom" size="14" color="#9ca3af"></uni-icons>
</view>
</picker>
</view>
<picker :range="moldLabels" :value="moldIndex" :disabled="readonlyBase" @change="onMoldChange">
<view :class="['picker-field', !formData.moldId ? 'is-placeholder' : '']">{{ selectedMoldLabel }}</view>
</picker>
</view>
<view class="form-item">
@ -226,15 +238,17 @@
<view class="footer-btn cancel" @click="goBack">{{ t('dashboard.back') }}</view>
<view v-if="!isReadonly" class="footer-btn confirm" @click="submitForm">{{ submitButtonText }}</view>
</view>
<sv-focus-no-keyboard ref="focusNoKeyboardRef"></sv-focus-no-keyboard>
</view>
</template>
<script setup>
import { computed, reactive, ref } from 'vue'
import { onLoad, onShow } from '@dcloudio/uni-app'
import { computed, reactive, ref, nextTick, watch } from 'vue'
import { onLoad, onReady, onShow } from '@dcloudio/uni-app'
import { useI18n } from 'vue-i18n'
import NavBar from '@/components/common/NavBar.vue'
import { getBrandList } from '@/api/mes/mold'
import { getBrandList, getMoldBrandPage } from '@/api/mes/mold'
import {
createMoldRepair,
getMoldRepair,
@ -252,6 +266,21 @@ const repairId = ref('')
const users = ref([])
const moldOptions = ref([])
const loading = ref(false)
const moldCodeSearch = ref('')
const focusNoKeyboardRef = ref(null)
const keywordInputSelector = '#mold-repair-scan-input input, input#mold-repair-scan-input'
//
let scanTimer = null
watch(moldCodeSearch, (val) => {
if (readonlyBase.value) return
if (!val || !val.trim()) return
clearTimeout(scanTimer)
// 100ms300ms
scanTimer = setTimeout(() => {
doMoldCodeSearch(val.trim())
}, 300)
})
const today = getTodayDate()
const formData = reactive({
@ -338,10 +367,22 @@ onLoad(async (options) => {
}
})
onReady(() => {
focusKeywordNoKeyboard()
})
onShow(() => {
applySelectedUser()
})
function focusKeywordNoKeyboard() {
nextTick(() => {
setTimeout(() => {
focusNoKeyboardRef.value?.focus(keywordInputSelector)
}, 80)
})
}
async function fetchUsers() {
try {
const res = await getSimpleUserList()
@ -447,63 +488,150 @@ function onMoldChange(event) {
applyMoldOption(current)
}
async function handleMoldScan() {
try {
const res = await uni.scanCode({ scanType: ['qrCode', 'barCode'] })
const scan = parseMoldScanResult(res?.result)
if (!scan.id) {
uni.showToast({ title: t('moldRepair.scanMoldRequired'), icon: 'none' })
return
function onMoldCodeSearch(event) {
const code = (event?.detail?.value || '').trim()
if (code) doMoldCodeSearch(code)
}
async function doMoldCodeSearch(rawCode) {
if (readonlyBase.value) return
if (!rawCode) return
if (scanTimer) clearTimeout(scanTimer)
// JSONID
const scan = parseScanMoldResult(rawCode)
let code = scan.code || ''
let moldId = scan.id || ''
// typeMOLD
if (scan.type && scan.type !== 'MOLD') {
uni.showToast({ title: t('moldRepair.moldNotFound'), icon: 'none' })
reFocusScanInput()
return
}
// IDcode
if (!moldId && !code) {
code = rawCode.trim()
}
// code
if (!code) code = rawCode.trim()
const lower = code.toLowerCase()
// moldIdcode
if (!moldId) {
if (code.toUpperCase().startsWith('MOLD-') || code.toUpperCase().startsWith('MOLD:')) {
moldId = code.replace(/MOLD[-:]/i, '')
} else {
const idMatch = code.match(/(\d+)$/)
if (idMatch) moldId = idMatch[1]
}
const matched = moldOptions.value.find((item) => String(item.value) === String(scan.id))
if (!matched) {
uni.showToast({ title: t('moldRepair.moldNotFound'), icon: 'none' })
return
}
// 1. ID
let matched = moldOptions.value.find((item) => String(item.value) === String(moldId))
// 2. ID /
if (!matched) {
matched = moldOptions.value.find((item) => {
const itemCode = String(item.raw?.code ?? item.raw?.moldCode ?? item.raw?.brandCode ?? '').trim().toLowerCase()
const itemName = String(item.raw?.name ?? item.raw?.moldName ?? item.raw?.brandName ?? '').trim().toLowerCase()
const itemLabel = String(item.label || '').toLowerCase()
return itemCode === lower || String(item.value) === code || itemCode.includes(lower) || itemName.includes(lower) || itemLabel.includes(lower)
})
}
// 3.
if (!matched) {
try {
const res = await getMoldBrandPage({ code, name: code, pageNo: 1, pageSize: 20 })
const root = res && res.data !== undefined ? res.data : res
const pageResult = root?.pageResult || root?.data?.pageResult || root?.data || root || {}
const list = pageResult?.list || pageResult?.rows || pageResult?.records || []
if (list.length) {
list.forEach((item) => {
if (item && item.id !== undefined && !moldOptions.value.some((opt) => String(opt.value) === String(item.id))) {
const itemCodeVal = item?.code ?? item?.brandCode ?? ''
const itemNameVal = item?.name ?? item?.brandName ?? ''
const labelStr = `${itemCodeVal} ${itemNameVal}`.trim() || String(item.id)
moldOptions.value.push({ value: item.id, label: labelStr, raw: item })
}
})
matched = moldOptions.value.find((item) => {
const itemCode = String(item.raw?.code ?? item.raw?.moldCode ?? item.raw?.brandCode ?? '').trim().toLowerCase()
return itemCode === lower || String(item.value) === code
})
}
} catch {
//
}
}
if (matched) {
applyMoldOption(matched)
} catch (error) {
const message = String(error?.errMsg || '')
if (message.includes('cancel')) return
uni.showToast({ title: t('moldRepair.scanFailed'), icon: 'none' })
moldCodeSearch.value = ''
} else {
uni.showToast({ title: t('moldRepair.moldNotFound'), icon: 'none' })
}
reFocusScanInput()
}
function parseMoldScanResult(result) {
// JSON
function parseScanMoldResult(result) {
const text = String(result || '').trim()
const directMatch = text.match(/^([A-Z_]+)-(\d+)$/i)
if (directMatch) {
return {
type: directMatch[1].toUpperCase(),
id: directMatch[1].toUpperCase() === 'MOLD' ? directMatch[2] : ''
}
}
// JSON
try {
const parsed = JSON.parse(text)
const type = String(parsed?.type || parsed?.bizType || parsed?.codeType || '').toUpperCase()
const id = String(parsed?.id || parsed?.moldId || parsed?.deviceId || '').trim()
return {
type,
id: type === 'MOLD' && id ? id : ''
}
const codeVal = String(parsed?.code || parsed?.moldCode || parsed?.deviceCode || '').trim()
return { type, id: id || '', code: codeVal || '' }
} catch {
return { type: '', id: '' }
// JSON
}
// MOLD-136
const directMatch = text.match(/^([A-Z_]+)-(\d+)$/i)
if (directMatch) {
return { type: directMatch[1].toUpperCase(), id: directMatch[2], code: text }
}
return { type: '', id: '', code: text }
}
function reFocusScanInput() {
nextTick(() => {
setTimeout(() => {
focusNoKeyboardRef.value?.focus(keywordInputSelector)
}, 150)
})
}
function applyMoldOption(current) {
formData.moldId = current.value
formData.moldCode = inputValue(current.raw?.code ?? current.raw?.moldCode)
formData.moldName = inputValue(current.raw?.name ?? current.raw?.moldName)
formData.specModel = inputValue(current.raw?.spec ?? current.raw?.moldSpec ?? current.raw?.machinerySpec)
formData.brand = inputValue(current.raw?.brand ?? current.raw?.moldBrand ?? current.raw?.machineryBrand)
}
function onShutdownChange(event) {
const value = String(event?.detail?.value || '')
if (value === 'true') formData.isShutdown = true
if (value === 'false') formData.isShutdown = false
if (value === 'true') selectShutdown(true)
if (value === 'false') selectShutdown(false)
}
function selectShutdown(value) {
if (readonlyMeta.value) return
formData.isShutdown = value
}
function onFaultLevelChange(event) {
formData.faultLevel = String(event?.detail?.value || '')
selectFaultLevel(event?.detail?.value)
}
function selectFaultLevel(value) {
if (readonlyMeta.value) return
formData.faultLevel = String(value || '')
}
function onRepairStatusChange(event) {
@ -766,10 +894,14 @@ function goBack() {
.page-container {
min-height: 100vh;
background: #f4f5f7;
display: flex;
flex-direction: column;
}
.form-scroll {
height: calc(100vh - 112rpx - env(safe-area-inset-bottom));
flex: 1;
overflow-y: auto;
padding-bottom: 140rpx;
}
.section-card {
@ -807,27 +939,6 @@ function goBack() {
color: #4b5563;
}
.form-label-row {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 14rpx;
}
.form-label-inline {
margin-bottom: 0;
}
.field-scan-btn {
width: 52rpx;
height: 52rpx;
border-radius: 12rpx;
background: #eef4fb;
display: flex;
align-items: center;
justify-content: center;
}
.form-label.required::after {
content: ' *';
color: #dc2626;
@ -869,6 +980,60 @@ function goBack() {
color: #9ca3af;
}
.mold-search-row {
display: flex;
align-items: center;
gap: 16rpx;
}
.mold-scan-input {
flex: 1;
min-width: 0;
height: 84rpx;
line-height: 84rpx;
padding: 0 20rpx;
border: 1rpx solid #d9dde5;
border-radius: 14rpx;
background: #ffffff;
font-size: 26rpx;
color: #374151;
box-sizing: border-box;
}
.mold-picker {
flex-shrink: 0;
width: 180rpx;
height: 84rpx;
line-height: 84rpx;
padding: 0 14rpx;
font-size: 26rpx;
color: #111827;
display: flex;
align-items: center;
justify-content: space-between;
gap: 8rpx;
border: 1rpx solid #d9dde5;
border-radius: 14rpx;
background: #ffffff;
box-sizing: border-box;
}
.mold-picker.is-placeholder {
color: #9ca3af;
}
.mold-picker.is-disabled {
color: #9ca3af;
}
.mold-picker .picker-field-text {
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.code-row {
display: flex;
align-items: center;
@ -915,6 +1080,46 @@ function goBack() {
color: #374151;
}
.native-radio {
position: absolute;
opacity: 0;
width: 0;
height: 0;
pointer-events: none;
}
.radio-dot {
width: 34rpx;
height: 34rpx;
flex: 0 0 34rpx;
border: 2rpx solid #d1d5db;
border-radius: 50%;
background: #ffffff;
box-sizing: border-box;
position: relative;
}
.radio-dot.checked {
border-color: #1f4b79;
background: #1f4b79;
}
.radio-dot.checked::after {
content: '';
position: absolute;
left: 9rpx;
top: 4rpx;
width: 10rpx;
height: 18rpx;
border: solid #ffffff;
border-width: 0 4rpx 4rpx 0;
transform: rotate(45deg);
}
.radio-dot.disabled {
opacity: 0.55;
}
.image-list {
display: flex;
flex-wrap: wrap;

Loading…
Cancel
Save