Merge remote-tracking branch 'origin/master'
commit
96e1ff570b
@ -0,0 +1,93 @@
|
||||
import upload from '@/utils/upload'
|
||||
import request from '@/utils/request'
|
||||
|
||||
// 模具点检工单分页
|
||||
export function getMoldCheckPage(params = {}) {
|
||||
return request({
|
||||
url: '/admin-api/mes/mold-ticket-management/page',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
// 模具点检结果分页
|
||||
export function getMoldCheckResultsPage(params = {}) {
|
||||
return request({
|
||||
url: '/admin-api/mes/mold-ticket-results/page',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
// 批量更新点检结果
|
||||
export function batchUpdateMoldCheckResults(data) {
|
||||
return request({
|
||||
url: '/admin-api/mes/mold-ticket-results/batchUpdate',
|
||||
method: 'put',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
// 图片上传
|
||||
export function uploadMoldCheckImage(filePath, name = 'file') {
|
||||
return upload({
|
||||
url: '/admin-api/infra/file/upload',
|
||||
name,
|
||||
filePath,
|
||||
showLoading: false
|
||||
})
|
||||
}
|
||||
|
||||
// 点检模板分页(项目表单)
|
||||
export function getPlanMaintenancePage(params = {}) {
|
||||
return request({
|
||||
url: '/admin-api/mes/mold-plan-maintenance/page',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
// 获取模板下的点检项
|
||||
export function getSubjectList(id) {
|
||||
return request({
|
||||
url: '/admin-api/mes/mold-plan-maintenance/getSubjectList',
|
||||
method: 'get',
|
||||
params: { id }
|
||||
})
|
||||
}
|
||||
|
||||
// 直接创建模具点检工单
|
||||
export function createMoldTicketDirect(data) {
|
||||
return request({
|
||||
url: '/admin-api/mes/mold-task-management/createMoldTicketDirect',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
// 获取模具详情
|
||||
export function getMoldBrandDetail(id) {
|
||||
return request({
|
||||
url: '/admin-api/erp/mold-brand/get',
|
||||
method: 'get',
|
||||
params: { id }
|
||||
})
|
||||
}
|
||||
|
||||
// 获取模具列表(选择模具用)
|
||||
export function getMoldList(params = {}) {
|
||||
return request({
|
||||
url: '/admin-api/erp/mold-brand/getMoldList',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
// 批量更新工单状态
|
||||
export function batchUpdateMoldCheckStatus(data) {
|
||||
return request({
|
||||
url: '/admin-api/mes/mold-ticket-management/batchUpdateStatus',
|
||||
method: 'put',
|
||||
data
|
||||
})
|
||||
}
|
||||
@ -0,0 +1,560 @@
|
||||
<template>
|
||||
<view class="page-container">
|
||||
<NavBar :title="t('moldCheck.moduleName')" />
|
||||
|
||||
<view class="filter-bar">
|
||||
<view class="keyword-box">
|
||||
<input v-model="searchKeyword" class="keyword-input" :placeholder="t('moldCheck.searchPlaceholder')"
|
||||
:focus="keywordFocus" confirm-type="search" @blur="keywordFocus = false" @input="handleKeywordInput"
|
||||
@confirm="handleSearch" />
|
||||
</view>
|
||||
<picker mode="selector" :range="jobStatusLabels" :value="jobStatusIndex" @change="onJobStatusChange">
|
||||
<view class="status-box">
|
||||
<text class="status-box-text">{{ currentJobStatusLabel }}</text>
|
||||
<uni-icons type="bottom" size="14" color="#9ca3af"></uni-icons>
|
||||
</view>
|
||||
</picker>
|
||||
<view class="reset-filter-btn" @click="resetFilters">{{ t('functionCommon.reset') }}</view>
|
||||
</view>
|
||||
|
||||
<scroll-view scroll-y class="list-scroll" :scroll-top="scrollTop" @scroll="onScroll" @scrolltolower="loadMore"
|
||||
:lower-threshold="80">
|
||||
<view class="list-wrap">
|
||||
<view v-for="item in list" :key="item.id" class="task-card" @click="openDetail(item)">
|
||||
<view class="card-header">
|
||||
<view class="header-main">
|
||||
<view class="header-left">
|
||||
<text
|
||||
:class="['task-type-tag', String(item.planType) === '1' ? 'tag-inspect' : 'tag-maintain']">{{
|
||||
taskTypeText(item.planType) }}</text>
|
||||
<text class="task-name">{{ textValue(item.planNo) }}</text>
|
||||
</view>
|
||||
<view class="header-tags">
|
||||
<text :class="['record-tag', jobStatusClass(item.jobStatus)]">{{
|
||||
jobStatusText(item.jobStatus) }}</text>
|
||||
<text :class="['record-tag', jobResultClass(item.jobResult)]">{{
|
||||
jobResultText(item.jobResult) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="card-body">
|
||||
<view class="row">
|
||||
<text class="label">{{ t('moldCheck.moldName') }}</text>
|
||||
<text class="value">{{ textValue(item.moldName) }}</text>
|
||||
</view>
|
||||
<view class="row">
|
||||
<text class="label">{{ t('moldCheck.template') }}</text>
|
||||
<text class="value">{{ textValue(item.configName) }}</text>
|
||||
</view>
|
||||
<view class="row">
|
||||
<text class="label">{{ t('moldCheck.operator') }}</text>
|
||||
<text class="value">{{ textValue(item.operatorName) }}</text>
|
||||
</view>
|
||||
<view class="row">
|
||||
<text class="label">{{ t('moldCheck.taskTime') }}</text>
|
||||
<text class="value">{{ formatDateTime(item.taskTime) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
|
||||
<view v-if="loading && pageNo === 1" class="hint">{{ t('functionCommon.loading') }}</view>
|
||||
<view v-else-if="!list.length" class="hint">{{ t('moldCheck.empty') }}</view>
|
||||
<view v-else-if="loadingMore" class="hint">{{ t('functionCommon.loadingMore') }}</view>
|
||||
<view v-else-if="finished" class="hint">{{ t('functionCommon.noMoreData') }}</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<view v-if="showGoTop" class="go-top-btn" @click="goTop">
|
||||
<uni-icons type="arrow-up" size="20" color="#1a3a5c"></uni-icons>
|
||||
</view>
|
||||
|
||||
<view class="add-btn" @click="goAdd">
|
||||
<text class="add-icon">+</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, nextTick, ref } from 'vue'
|
||||
import { onLoad, onReachBottom, onShow, onUnload } from '@dcloudio/uni-app'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import NavBar from '@/components/common/NavBar.vue'
|
||||
import { getMoldCheckPage } from '@/api/mes/moldCheck'
|
||||
import { DICT_TYPE, getDictLabel, initAllDict, useDict } from '@/utils/dict'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const searchKeyword = ref('')
|
||||
const selectedJobStatus = ref('0')
|
||||
const list = ref([])
|
||||
const loading = ref(false)
|
||||
const loadingMore = ref(false)
|
||||
const finished = ref(false)
|
||||
const pageNo = ref(1)
|
||||
const pageSize = ref(10)
|
||||
const scrollTop = ref(0)
|
||||
const showGoTop = ref(false)
|
||||
const keywordFocus = ref(false)
|
||||
|
||||
let searchTimer = null
|
||||
|
||||
// 从字典获取作业状态选项
|
||||
const { job_status: jobStatusDictOptions } = useDict(DICT_TYPE.JOB_STATUS)
|
||||
|
||||
const jobStatusOptions = computed(() => {
|
||||
const dictList = jobStatusDictOptions.value || []
|
||||
return [
|
||||
{ label: t('functionCommon.all'), value: '' },
|
||||
...dictList.map((item) => ({ label: item.label, value: item.value }))
|
||||
]
|
||||
})
|
||||
|
||||
const jobStatusLabels = computed(() => jobStatusOptions.value.map((item) => item.label))
|
||||
const jobStatusIndex = computed(() => {
|
||||
const index = jobStatusOptions.value.findIndex((item) => item.value === selectedJobStatus.value)
|
||||
return index >= 0 ? index : 0
|
||||
})
|
||||
const currentJobStatusLabel = computed(() => {
|
||||
const current = jobStatusOptions.value.find((item) => item.value === selectedJobStatus.value)
|
||||
return current ? current.label : t('functionCommon.all')
|
||||
})
|
||||
|
||||
onLoad(async () => {
|
||||
await initAllDict()
|
||||
await fetchList(true)
|
||||
})
|
||||
|
||||
onShow(() => {
|
||||
activateKeywordFocus()
|
||||
if (uni.getStorageSync('moldCheckListNeedRefresh') === '1') {
|
||||
uni.removeStorageSync('moldCheckListNeedRefresh')
|
||||
fetchList(true)
|
||||
}
|
||||
})
|
||||
|
||||
onUnload(() => {
|
||||
clearSearchTimer()
|
||||
})
|
||||
|
||||
function normalizePageData(res) {
|
||||
const root = res && res.data !== undefined ? res.data : res
|
||||
const candidateList = root?.list || root?.rows || root?.records || root?.data?.list || root?.data?.rows || root?.data?.records || []
|
||||
const candidateTotal = root?.total ?? root?.data?.total ?? (Array.isArray(candidateList) ? candidateList.length : 0)
|
||||
return {
|
||||
list: Array.isArray(candidateList) ? candidateList : [],
|
||||
total: Number(candidateTotal || 0)
|
||||
}
|
||||
}
|
||||
|
||||
onReachBottom(() => {
|
||||
loadMore()
|
||||
})
|
||||
|
||||
async function fetchList(reset) {
|
||||
if (reset) {
|
||||
pageNo.value = 1
|
||||
finished.value = false
|
||||
}
|
||||
|
||||
if (pageNo.value === 1) {
|
||||
loading.value = true
|
||||
} else {
|
||||
loadingMore.value = true
|
||||
}
|
||||
|
||||
try {
|
||||
const params = {
|
||||
pageNo: pageNo.value,
|
||||
pageSize: pageSize.value,
|
||||
planNo: searchKeyword.value.trim() || undefined,
|
||||
planType: '1',
|
||||
jobStatus: selectedJobStatus.value || undefined
|
||||
}
|
||||
const res = await getMoldCheckPage(params)
|
||||
const page = normalizePageData(res)
|
||||
list.value = reset ? page.list : [...list.value, ...page.list]
|
||||
finished.value = list.value.length >= page.total || page.list.length < pageSize.value
|
||||
} catch (error) {
|
||||
if (!reset) pageNo.value = Math.max(1, pageNo.value - 1)
|
||||
uni.showToast({ title: t('functionCommon.loadFailed'), icon: 'none' })
|
||||
} finally {
|
||||
loading.value = false
|
||||
loadingMore.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function handleSearch() {
|
||||
clearSearchTimer()
|
||||
uni.hideKeyboard()
|
||||
fetchList(true)
|
||||
}
|
||||
|
||||
function handleKeywordInput() {
|
||||
clearSearchTimer()
|
||||
searchTimer = setTimeout(() => {
|
||||
fetchList(true)
|
||||
}, 300)
|
||||
}
|
||||
|
||||
async function resetFilters() {
|
||||
clearSearchTimer()
|
||||
searchKeyword.value = ''
|
||||
selectedJobStatus.value = '0'
|
||||
activateKeywordFocus()
|
||||
await nextTick()
|
||||
await fetchList(true)
|
||||
}
|
||||
|
||||
function onJobStatusChange(event) {
|
||||
const index = Number(event?.detail?.value || 0)
|
||||
selectedJobStatus.value = jobStatusOptions.value[index]?.value ?? ''
|
||||
fetchList(true)
|
||||
}
|
||||
|
||||
async function loadMore() {
|
||||
if (loading.value || loadingMore.value || finished.value) return
|
||||
pageNo.value += 1
|
||||
await fetchList(false)
|
||||
}
|
||||
|
||||
function openDetail(item) {
|
||||
if (!item?.id) {
|
||||
uni.showToast({ title: t('functionCommon.noIdView'), icon: 'none' })
|
||||
return
|
||||
}
|
||||
uni.setStorageSync('moldCheckDetail', JSON.stringify(item))
|
||||
uni.navigateTo({
|
||||
url: `/pages_function/pages/moldCheck/detail?id=${encodeURIComponent(String(item.id))}`
|
||||
})
|
||||
}
|
||||
|
||||
function goAdd() {
|
||||
uni.navigateTo({
|
||||
url: '/pages_function/pages/moldCheck/add'
|
||||
})
|
||||
}
|
||||
|
||||
function onScroll(event) {
|
||||
showGoTop.value = (event?.detail?.scrollTop || 0) > 600
|
||||
}
|
||||
|
||||
function goTop() {
|
||||
scrollTop.value = 0
|
||||
}
|
||||
|
||||
function activateKeywordFocus() {
|
||||
keywordFocus.value = false
|
||||
nextTick(() => {
|
||||
keywordFocus.value = true
|
||||
})
|
||||
}
|
||||
|
||||
function clearSearchTimer() {
|
||||
if (searchTimer) {
|
||||
clearTimeout(searchTimer)
|
||||
searchTimer = null
|
||||
}
|
||||
}
|
||||
|
||||
function taskTypeText(value) {
|
||||
const normalized = String(value)
|
||||
if (normalized === '1') return t('moldCheck.taskTypeInspect')
|
||||
if (normalized === '2') return t('moldCheck.taskTypeMaintain')
|
||||
return textValue(value)
|
||||
}
|
||||
|
||||
function jobStatusText(value) {
|
||||
return getDictLabel(DICT_TYPE.JOB_STATUS, value, textValue(value))
|
||||
}
|
||||
|
||||
function jobStatusClass(value) {
|
||||
const normalized = String(value)
|
||||
if (normalized === '2') return 'text-primary'
|
||||
if (normalized === '1') return 'text-success'
|
||||
if (normalized === '3') return 'text-danger'
|
||||
if (normalized === '4') return 'text-muted'
|
||||
return 'text-warning'
|
||||
}
|
||||
|
||||
function jobResultText(value) {
|
||||
const normalized = String(value).trim().toUpperCase()
|
||||
if (normalized === '1' || normalized === 'OK') return t('moldCheck.jobResultOk')
|
||||
if (normalized === '2' || normalized === 'NG') return t('moldCheck.jobResultNg')
|
||||
if (!normalized || normalized === '0') return '-'
|
||||
return textValue(value)
|
||||
}
|
||||
|
||||
function jobResultClass(value) {
|
||||
const normalized = String(value).trim().toUpperCase()
|
||||
if (normalized === '1' || normalized === 'OK') return 'text-success'
|
||||
if (normalized === '2' || normalized === 'NG') return 'text-danger'
|
||||
return ''
|
||||
}
|
||||
|
||||
function textValue(value) {
|
||||
if (value === 0) return '0'
|
||||
if (value === null || value === undefined) return '-'
|
||||
const text = String(value).trim()
|
||||
return text || '-'
|
||||
}
|
||||
|
||||
function formatDateTime(value) {
|
||||
if (!value) return '-'
|
||||
if (Array.isArray(value) && value.length >= 3) {
|
||||
const [year, month, day, hour = 0, minute = 0, second = 0] = value
|
||||
const pad = (num) => String(num).padStart(2, '0')
|
||||
return `${year}-${pad(month)}-${pad(day)} ${pad(hour)}:${pad(minute)}:${pad(second)}`
|
||||
}
|
||||
const date = new Date(Number(value))
|
||||
if (Number.isNaN(date.getTime())) return textValue(value)
|
||||
const pad = (num) => String(num).padStart(2, '0')
|
||||
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page-container {
|
||||
min-height: 100vh;
|
||||
background: #f4f5f7;
|
||||
}
|
||||
|
||||
.filter-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12rpx;
|
||||
padding: 18rpx 4rpx 0rpx;
|
||||
}
|
||||
|
||||
.keyword-box,
|
||||
.status-box,
|
||||
.reset-filter-btn {
|
||||
min-width:100rpx;
|
||||
height: 66rpx;
|
||||
background: #ffffff;
|
||||
border: 1rpx solid #d9dde5;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.keyword-box {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
padding: 0 28rpx;
|
||||
}
|
||||
|
||||
.keyword-input {
|
||||
width: 100%;
|
||||
font-size: 24rpx;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
.status-box {
|
||||
flex-shrink: 0;
|
||||
justify-content: space-between;
|
||||
padding: 0 28rpx;
|
||||
min-width: 160rpx;
|
||||
}
|
||||
|
||||
.status-box-text {
|
||||
font-size: 24rpx;
|
||||
color: #374151;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.reset-filter-btn {
|
||||
justify-content: center;
|
||||
font-size: 24rpx;
|
||||
color: #4b5563;
|
||||
}
|
||||
|
||||
.list-scroll {
|
||||
height: calc(100vh - 194rpx);
|
||||
}
|
||||
|
||||
.list-wrap {
|
||||
padding: 0 4rpx 140rpx;
|
||||
}
|
||||
|
||||
.task-card {
|
||||
position: relative;
|
||||
margin-top: 20rpx;
|
||||
padding: 28rpx;
|
||||
background: #fff;
|
||||
border-radius: 22rpx;
|
||||
box-shadow: 0 8rpx 28rpx rgba(15, 23, 42, 0.06);
|
||||
}
|
||||
|
||||
.card-header {
|
||||
margin-bottom: 18rpx;
|
||||
}
|
||||
|
||||
.header-main {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.header-tags {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.task-name {
|
||||
flex: 1;
|
||||
font-size: 32rpx;
|
||||
font-weight: 700;
|
||||
color: #0f172a;
|
||||
margin-left: 12rpx
|
||||
}
|
||||
|
||||
.task-type-tag {
|
||||
padding: 8rpx 18rpx;
|
||||
border-radius: 8rpx;
|
||||
font-size: 22rpx;
|
||||
}
|
||||
|
||||
.record-tag {
|
||||
padding: 8rpx 18rpx;
|
||||
border-radius: 999rpx;
|
||||
font-size: 22rpx;
|
||||
line-height: 1;
|
||||
background: #e2e8f0;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.tag-inspect {
|
||||
color: #1d4ed8;
|
||||
background: #dbeafe;
|
||||
}
|
||||
|
||||
.tag-maintain {
|
||||
color: #15803d;
|
||||
background: #dcfce7;
|
||||
}
|
||||
|
||||
.card-body .row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.label {
|
||||
width: 180rpx;
|
||||
font-size: 25rpx;
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
.value {
|
||||
flex: 1;
|
||||
text-align: right;
|
||||
font-size: 27rpx;
|
||||
color: #334155;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.text-success {
|
||||
color: #16a34a;
|
||||
}
|
||||
|
||||
.text-danger {
|
||||
color: #dc2626;
|
||||
}
|
||||
|
||||
.text-warning {
|
||||
color: #f59e0b;
|
||||
}
|
||||
|
||||
.text-primary {
|
||||
color: #2563eb;
|
||||
}
|
||||
|
||||
.text-muted {
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
.record-tag.text-success {
|
||||
color: #15803d;
|
||||
background: #dcfce7;
|
||||
}
|
||||
|
||||
.record-tag.text-danger {
|
||||
color: #dc2626;
|
||||
background: #fee2e2;
|
||||
}
|
||||
|
||||
.record-tag.text-warning {
|
||||
color: #d97706;
|
||||
background: #fef3c7;
|
||||
}
|
||||
|
||||
.record-tag.text-primary {
|
||||
color: #1d4ed8;
|
||||
background: #dbeafe;
|
||||
}
|
||||
|
||||
.record-tag.text-muted {
|
||||
color: #64748b;
|
||||
background: #e2e8f0;
|
||||
}
|
||||
|
||||
.hint {
|
||||
padding: 36rpx 0;
|
||||
text-align: center;
|
||||
color: #94a3b8;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
|
||||
.go-top-btn {
|
||||
position: fixed;
|
||||
right: 28rpx;
|
||||
bottom: calc(140rpx + env(safe-area-inset-bottom));
|
||||
width: 88rpx;
|
||||
height: 88rpx;
|
||||
border-radius: 44rpx;
|
||||
background: rgba(255, 255, 255, 0.96);
|
||||
box-shadow: 0 8rpx 24rpx rgba(15, 23, 42, 0.12);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.add-btn,
|
||||
.go-top-btn {
|
||||
position: fixed;
|
||||
right: 32rpx;
|
||||
width: 92rpx;
|
||||
height: 92rpx;
|
||||
border-radius: 46rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 14rpx 30rpx rgba(24, 63, 108, 0.24);
|
||||
}
|
||||
|
||||
.add-btn {
|
||||
right: 28rpx;
|
||||
bottom: calc(56rpx + env(safe-area-inset-bottom));
|
||||
background: #1f4b79;
|
||||
}
|
||||
|
||||
.add-icon {
|
||||
color: #ffffff;
|
||||
font-size: 64rpx;
|
||||
line-height: 1;
|
||||
margin-top: -4rpx;
|
||||
}
|
||||
</style>
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue