feat:新增产品管理模块
parent
493fd0125f
commit
3f496dea47
@ -0,0 +1,48 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function getProductCategoryList(params) {
|
||||
return request({
|
||||
url: '/admin-api/erp/product-category/list',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
export function getProductCategorySimpleList() {
|
||||
return request({
|
||||
url: '/admin-api/erp/product-category/simple-list',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
export function getProductCategory(id) {
|
||||
return request({
|
||||
url: '/admin-api/erp/product-category/get',
|
||||
method: 'get',
|
||||
params: { id }
|
||||
})
|
||||
}
|
||||
|
||||
export function createProductCategory(data) {
|
||||
return request({
|
||||
url: '/admin-api/erp/product-category/create',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
export function updateProductCategory(data) {
|
||||
return request({
|
||||
url: '/admin-api/erp/product-category/update',
|
||||
method: 'put',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
export function deleteProductCategory(id) {
|
||||
return request({
|
||||
url: '/admin-api/erp/product-category/delete',
|
||||
method: 'delete',
|
||||
params: { id }
|
||||
})
|
||||
}
|
||||
@ -0,0 +1,70 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function getProductPage(params) {
|
||||
return request({
|
||||
url: '/admin-api/erp/product/page',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
export function getProductSimpleList(params) {
|
||||
return request({
|
||||
url: '/admin-api/erp/product/simple-list-all',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
export function getMesProductSimpleList() {
|
||||
return request({
|
||||
url: '/admin-api/erp/product/simple-list-product',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
export function getItemSimpleList() {
|
||||
return request({
|
||||
url: '/admin-api/erp/product/simple-list-item',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
export function getProduct(id) {
|
||||
return request({
|
||||
url: '/admin-api/erp/product/get',
|
||||
method: 'get',
|
||||
params: { id }
|
||||
})
|
||||
}
|
||||
|
||||
export function createProduct(data) {
|
||||
return request({
|
||||
url: '/admin-api/erp/product/create',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
export function updateProduct(data) {
|
||||
return request({
|
||||
url: '/admin-api/erp/product/update',
|
||||
method: 'put',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
export function deleteProduct(id) {
|
||||
return request({
|
||||
url: '/admin-api/erp/product/delete',
|
||||
method: 'delete',
|
||||
params: { id }
|
||||
})
|
||||
}
|
||||
|
||||
export function getProductUnitSimpleList() {
|
||||
return request({
|
||||
url: '/admin-api/erp/product-unit/simple-list',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
@ -0,0 +1,49 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export function getBomPage(params) {
|
||||
return request({
|
||||
url: '/admin-api/mes/bom/page',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
export function getBom(id) {
|
||||
return request({
|
||||
url: '/admin-api/mes/bom/get',
|
||||
method: 'get',
|
||||
params: { id }
|
||||
})
|
||||
}
|
||||
|
||||
export function createBom(data) {
|
||||
return request({
|
||||
url: '/admin-api/mes/bom/create',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
export function updateBom(data) {
|
||||
return request({
|
||||
url: '/admin-api/mes/bom/update',
|
||||
method: 'put',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
export function deleteBom(id) {
|
||||
return request({
|
||||
url: '/admin-api/mes/bom/delete',
|
||||
method: 'delete',
|
||||
params: { id }
|
||||
})
|
||||
}
|
||||
|
||||
export function getBomDetailListByBomId(bomId) {
|
||||
return request({
|
||||
url: '/admin-api/mes/bom/bom-detail/list-by-bom-id',
|
||||
method: 'get',
|
||||
params: { bomId }
|
||||
})
|
||||
}
|
||||
@ -0,0 +1,155 @@
|
||||
<template>
|
||||
<view class="page-container">
|
||||
<view class="fixed-header">
|
||||
<AppTitleHeader :title="t('materialCategory.detailTitle')" />
|
||||
</view>
|
||||
|
||||
<scroll-view scroll-y class="detail-scroll">
|
||||
<view class="content-section">
|
||||
<!-- 基础信息 -->
|
||||
<view class="info-card">
|
||||
<view class="card-title">{{ t('materialCategory.basicInfo') }}</view>
|
||||
<view class="info-list">
|
||||
<view class="info-row">
|
||||
<text class="info-label">{{ t('materialCategory.code') }}</text>
|
||||
<text class="info-value">{{ fieldValue('code') }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="info-label">{{ t('materialCategory.name') }}</text>
|
||||
<text class="info-value">{{ fieldValue('name') }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="info-label">{{ t('materialCategory.parentName') }}</text>
|
||||
<text class="info-value">{{ parentNameText }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="info-label">{{ t('materialCategory.sort') }}</text>
|
||||
<text class="info-value">{{ fieldValue('sort') }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="info-label">{{ t('materialCategory.status') }}</text>
|
||||
<text :class="['info-value', String(detailData?.status) === '0' ? 'text-success' : 'text-danger']">{{ statusText }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="info-label">{{ t('materialCategory.createTime') }}</text>
|
||||
<text class="info-value">{{ formatDateTime(detailData?.createTime) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import AppTitleHeader from '@/components/common/AppTitleHeader.vue'
|
||||
import { getProductCategory, getProductCategoryList } from '@/api/erp/productCategory'
|
||||
import { DICT_TYPE, getDictLabel, initAllDict } from '@/utils/dict'
|
||||
|
||||
const { t } = useI18n()
|
||||
const detailId = ref(undefined)
|
||||
const detailData = ref(null)
|
||||
const allCategories = ref([])
|
||||
|
||||
const parentNameText = computed(() => {
|
||||
if (!detailData.value?.parentId || detailData.value.parentId === 0) return t('materialCategory.rootCategory')
|
||||
const parent = allCategories.value.find(item => item.id === detailData.value.parentId)
|
||||
return parent ? (parent.name || '-') : '-'
|
||||
})
|
||||
|
||||
const statusText = computed(() => {
|
||||
return getDictLabel(DICT_TYPE.COMMON_STATUS, detailData.value?.status, textValue(detailData.value?.status))
|
||||
})
|
||||
|
||||
onLoad(async (query) => {
|
||||
const id = query?.id ? decodeURIComponent(String(query.id)) : ''
|
||||
detailId.value = id || undefined
|
||||
await initAllDict()
|
||||
await fetchDetail()
|
||||
})
|
||||
|
||||
async function fetchDetail() {
|
||||
if (!detailId.value) {
|
||||
uni.showToast({ title: t('materialCategory.noId'), icon: 'none' })
|
||||
return
|
||||
}
|
||||
try {
|
||||
const res = await getProductCategory(detailId.value)
|
||||
detailData.value = normalizeDetail(res)
|
||||
const listRes = await getProductCategoryList()
|
||||
allCategories.value = normalizeListData(listRes)
|
||||
} catch (e) {
|
||||
uni.showToast({ title: t('materialCategory.loadFailed'), icon: 'none' })
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeDetail(res) {
|
||||
const root = res && res.data !== undefined ? res.data : res
|
||||
if (root?.data && typeof root.data === 'object') return root.data
|
||||
if (root && typeof root === 'object') return root
|
||||
return {}
|
||||
}
|
||||
|
||||
function normalizeListData(res) {
|
||||
const root = res && res.data !== undefined ? res.data : res
|
||||
if (Array.isArray(root)) return root
|
||||
if (root?.data && Array.isArray(root.data)) return root.data
|
||||
return []
|
||||
}
|
||||
|
||||
function fieldValue(field) {
|
||||
return textValue(detailData.value ? detailData.value[field] : undefined)
|
||||
}
|
||||
|
||||
function textValue(value) {
|
||||
if (value === 0) return '0'
|
||||
if (value === false) return t('functionCommon.no')
|
||||
if (value === true) return t('functionCommon.yes')
|
||||
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 [y, m, d, hh = 0, mm = 0, ss = 0] = value
|
||||
const pad = (n) => String(n).padStart(2, '0')
|
||||
return `${y}-${pad(m)}-${pad(d)} ${pad(hh)}:${pad(mm)}:${pad(ss)}`
|
||||
}
|
||||
const text = String(value).trim()
|
||||
if (!text) return '-'
|
||||
const numeric = Number(text)
|
||||
if (Number.isFinite(numeric)) {
|
||||
const timestamp = text.length === 10 ? numeric * 1000 : numeric
|
||||
const date = new Date(timestamp)
|
||||
if (!Number.isNaN(date.getTime())) {
|
||||
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')} ${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}:${String(date.getSeconds()).padStart(2, '0')}`
|
||||
}
|
||||
}
|
||||
const date = new Date(text)
|
||||
if (!Number.isNaN(date.getTime())) {
|
||||
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')} ${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}:${String(date.getSeconds()).padStart(2, '0')}`
|
||||
}
|
||||
return text
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page-container { min-height: 100vh; background-color: #f0f2f5; }
|
||||
.fixed-header { position: sticky; top: 0; z-index: 10; }
|
||||
.detail-scroll { height: calc(100vh - 120rpx); }
|
||||
.content-section { padding: 0 24rpx 24rpx; }
|
||||
.info-card { margin-top: 20rpx; background: #ffffff; border-radius: 20rpx; padding: 28rpx; box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05); }
|
||||
.card-title { font-size: 32rpx; color: #1a3a5c; font-weight: 700; margin-bottom: 18rpx; }
|
||||
.info-list { background: #ffffff; }
|
||||
.info-row { display: flex; justify-content: space-between; align-items: flex-start; padding: 18rpx 0; border-bottom: 1rpx solid #edf0f3; }
|
||||
.info-row:last-child { border-bottom: none; }
|
||||
.info-label { font-size: 27rpx; color: #8a9099; width: 220rpx; }
|
||||
.info-value { flex: 1; text-align: right; font-size: 28rpx; color: #30363d; line-height: 1.45; }
|
||||
.text-success { color: #52c41a; }
|
||||
.text-danger { color: #ff4d4f; }
|
||||
</style>
|
||||
@ -0,0 +1,153 @@
|
||||
<template>
|
||||
<view class="page-container">
|
||||
<view class="fixed-header">
|
||||
<AppTitleHeader :title="t('materialInfo.detailTitle')" />
|
||||
</view>
|
||||
|
||||
<scroll-view scroll-y class="detail-scroll">
|
||||
<view class="content-section">
|
||||
<!-- 基础信息 -->
|
||||
<view class="info-card">
|
||||
<view class="card-title">{{ t('materialInfo.basicInfo') }}</view>
|
||||
<view class="info-list">
|
||||
<view class="info-row">
|
||||
<text class="info-label">{{ t('materialInfo.barCode') }}</text>
|
||||
<text class="info-value">{{ fieldValue('barCode') }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="info-label">{{ t('materialInfo.name') }}</text>
|
||||
<text class="info-value">{{ fieldValue('name') }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="info-label">{{ t('materialInfo.category') }}</text>
|
||||
<text class="info-value">{{ fieldValue('subCategoryName') }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="info-label">{{ t('materialInfo.unit') }}</text>
|
||||
<text class="info-value">{{ fieldValue('unitName') }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="info-label">{{ t('materialInfo.standard') }}</text>
|
||||
<text class="info-value">{{ fieldValue('standard') }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="info-label">{{ t('materialInfo.expiryDay') }}</text>
|
||||
<text class="info-value">{{ fieldValue('expiryDay') }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="info-label">{{ t('materialInfo.status') }}</text>
|
||||
<text :class="['info-value', String(detailData?.status) === '0' ? 'text-success' : 'text-danger']">{{ statusText }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="info-label">{{ t('materialInfo.createTime') }}</text>
|
||||
<text class="info-value">{{ formatDateTime(detailData?.createTime) }}</text>
|
||||
</view>
|
||||
<view class="info-row remark-row">
|
||||
<text class="info-label">{{ t('materialInfo.remark') }}</text>
|
||||
<text class="info-value remark-value">{{ fieldValue('remark') }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import AppTitleHeader from '@/components/common/AppTitleHeader.vue'
|
||||
import { getProduct } from '@/api/erp/productInfo'
|
||||
import { DICT_TYPE, getDictLabel, initAllDict } from '@/utils/dict'
|
||||
|
||||
const { t } = useI18n()
|
||||
const detailId = ref(undefined)
|
||||
const detailData = ref(null)
|
||||
|
||||
const statusText = computed(() => {
|
||||
return getDictLabel(DICT_TYPE.COMMON_STATUS, detailData.value?.status, textValue(detailData.value?.status))
|
||||
})
|
||||
|
||||
onLoad(async (query) => {
|
||||
const id = query?.id ? decodeURIComponent(String(query.id)) : ''
|
||||
detailId.value = id || undefined
|
||||
await initAllDict()
|
||||
await fetchDetail()
|
||||
})
|
||||
|
||||
async function fetchDetail() {
|
||||
if (!detailId.value) {
|
||||
uni.showToast({ title: t('materialInfo.noId'), icon: 'none' })
|
||||
return
|
||||
}
|
||||
try {
|
||||
const res = await getProduct(detailId.value)
|
||||
detailData.value = normalizeDetail(res)
|
||||
} catch (e) {
|
||||
uni.showToast({ title: t('materialInfo.loadFailed'), icon: 'none' })
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeDetail(res) {
|
||||
const root = res && res.data !== undefined ? res.data : res
|
||||
if (root?.data && typeof root.data === 'object') return root.data
|
||||
if (root && typeof root === 'object') return root
|
||||
return {}
|
||||
}
|
||||
|
||||
function fieldValue(field) {
|
||||
return textValue(detailData.value ? detailData.value[field] : undefined)
|
||||
}
|
||||
|
||||
function textValue(value) {
|
||||
if (value === 0) return '0'
|
||||
if (value === false) return t('functionCommon.no')
|
||||
if (value === true) return t('functionCommon.yes')
|
||||
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 [y, m, d, hh = 0, mm = 0, ss = 0] = value
|
||||
const pad = (n) => String(n).padStart(2, '0')
|
||||
return `${y}-${pad(m)}-${pad(d)} ${pad(hh)}:${pad(mm)}:${pad(ss)}`
|
||||
}
|
||||
const text = String(value).trim()
|
||||
if (!text) return '-'
|
||||
const numeric = Number(text)
|
||||
if (Number.isFinite(numeric)) {
|
||||
const timestamp = text.length === 10 ? numeric * 1000 : numeric
|
||||
const date = new Date(timestamp)
|
||||
if (!Number.isNaN(date.getTime())) {
|
||||
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')} ${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}:${String(date.getSeconds()).padStart(2, '0')}`
|
||||
}
|
||||
}
|
||||
const date = new Date(text)
|
||||
if (!Number.isNaN(date.getTime())) {
|
||||
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')} ${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}:${String(date.getSeconds()).padStart(2, '0')}`
|
||||
}
|
||||
return text
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page-container { min-height: 100vh; background-color: #f0f2f5; }
|
||||
.fixed-header { position: sticky; top: 0; z-index: 10; }
|
||||
.detail-scroll { height: calc(100vh - 120rpx); }
|
||||
.content-section { padding: 0 24rpx 24rpx; }
|
||||
.info-card { margin-top: 20rpx; background: #ffffff; border-radius: 20rpx; padding: 28rpx; box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05); }
|
||||
.card-title { font-size: 32rpx; color: #1a3a5c; font-weight: 700; margin-bottom: 18rpx; }
|
||||
.info-list { background: #ffffff; }
|
||||
.info-row { display: flex; justify-content: space-between; align-items: flex-start; padding: 18rpx 0; border-bottom: 1rpx solid #edf0f3; }
|
||||
.info-row:last-child { border-bottom: none; }
|
||||
.info-label { font-size: 27rpx; color: #8a9099; width: 220rpx; }
|
||||
.info-value { flex: 1; text-align: right; font-size: 28rpx; color: #30363d; line-height: 1.45; }
|
||||
.remark-row { border-bottom: none; }
|
||||
.remark-value { white-space: pre-wrap; }
|
||||
.text-success { color: #52c41a; }
|
||||
.text-danger { color: #ff4d4f; }
|
||||
</style>
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,209 @@
|
||||
<template>
|
||||
<view class="page-container">
|
||||
<view class="fixed-header">
|
||||
<AppTitleHeader :title="t('productBom.detailTitle')" />
|
||||
</view>
|
||||
|
||||
<scroll-view scroll-y class="detail-scroll">
|
||||
<view class="content-section">
|
||||
<!-- 基础信息 -->
|
||||
<view class="info-card">
|
||||
<view class="card-title">{{ t('productBom.basicInfo') }}</view>
|
||||
<view class="info-list">
|
||||
<view class="info-row">
|
||||
<text class="info-label">{{ t('productBom.code') }}</text>
|
||||
<text class="info-value">{{ fieldValue('code') }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="info-label">{{ t('productBom.version') }}</text>
|
||||
<text class="info-value">{{ fieldValue('version') }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="info-label">{{ t('productBom.product') }}</text>
|
||||
<text class="info-value">{{ fieldValue('productName') }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="info-label">{{ t('productBom.unit') }}</text>
|
||||
<text class="info-value">{{ fieldValue('unitName') }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="info-label">{{ t('productBom.yieldRate') }}</text>
|
||||
<text class="info-value">{{ fieldValue('yieldRate') }}%</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="info-label">{{ t('productBom.isEnable') }}</text>
|
||||
<text :class="['info-value', String(detailData?.isEnable) === 'true' || String(detailData?.isEnable) === '1' ? 'text-success' : 'text-danger']">{{ enableText }}</text>
|
||||
</view>
|
||||
<view class="info-row">
|
||||
<text class="info-label">{{ t('productBom.createTime') }}</text>
|
||||
<text class="info-value">{{ formatDateTime(detailData?.createTime) }}</text>
|
||||
</view>
|
||||
<view class="info-row remark-row">
|
||||
<text class="info-label">{{ t('productBom.remark') }}</text>
|
||||
<text class="info-value remark-value">{{ fieldValue('remark') }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- BOM明细Tab -->
|
||||
<view class="info-card">
|
||||
<view class="card-title">{{ t('productBom.detailTab') }}</view>
|
||||
<view v-if="bomDetailList.length" class="detail-list">
|
||||
<view v-for="(item, index) in bomDetailList" :key="item.id || index" class="detail-item">
|
||||
<view class="detail-item-header">
|
||||
<text class="detail-index">{{ index + 1 }}</text>
|
||||
<text class="detail-product-name">{{ textValue(item.productName) }}</text>
|
||||
</view>
|
||||
<view class="detail-item-body">
|
||||
<view class="detail-row">
|
||||
<text class="detail-label">{{ t('productBom.detailUsageNumber') }}</text>
|
||||
<text class="detail-value">{{ textValue(item.usageNumber) }}</text>
|
||||
</view>
|
||||
<view class="detail-row">
|
||||
<text class="detail-label">{{ t('productBom.detailUnit') }}</text>
|
||||
<text class="detail-value">{{ textValue(item.unitName) }}</text>
|
||||
</view>
|
||||
<view class="detail-row">
|
||||
<text class="detail-label">{{ t('productBom.detailLossRate') }}</text>
|
||||
<text class="detail-value">{{ textValue(item.yieldRate) }}%</text>
|
||||
</view>
|
||||
<view v-if="item.remark" class="detail-row">
|
||||
<text class="detail-label">{{ t('productBom.detailRemark') }}</text>
|
||||
<text class="detail-value">{{ textValue(item.remark) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view v-else class="detail-empty">{{ t('productBom.detailEmpty') }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import AppTitleHeader from '@/components/common/AppTitleHeader.vue'
|
||||
import { getBom, getBomDetailListByBomId } from '@/api/mes/productBom'
|
||||
import { DICT_TYPE, getDictLabel, initAllDict } from '@/utils/dict'
|
||||
|
||||
const { t } = useI18n()
|
||||
const detailId = ref(undefined)
|
||||
const detailData = ref(null)
|
||||
const bomDetailList = ref([])
|
||||
|
||||
const enableText = computed(() => {
|
||||
return getDictLabel(DICT_TYPE.INFRA_BOOLEAN_STRING, detailData.value?.isEnable, textValue(detailData.value?.isEnable))
|
||||
})
|
||||
|
||||
onLoad(async (query) => {
|
||||
const id = query?.id ? decodeURIComponent(String(query.id)) : ''
|
||||
detailId.value = id || undefined
|
||||
await initAllDict()
|
||||
await fetchDetail()
|
||||
})
|
||||
|
||||
async function fetchDetail() {
|
||||
if (!detailId.value) {
|
||||
uni.showToast({ title: t('productBom.noId'), icon: 'none' })
|
||||
return
|
||||
}
|
||||
try {
|
||||
const res = await getBom(detailId.value)
|
||||
detailData.value = normalizeDetail(res)
|
||||
await fetchBomDetails()
|
||||
} catch (e) {
|
||||
uni.showToast({ title: t('productBom.loadFailed'), icon: 'none' })
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchBomDetails() {
|
||||
try {
|
||||
const res = await getBomDetailListByBomId(detailId.value)
|
||||
bomDetailList.value = normalizeListData(res)
|
||||
} catch (e) {
|
||||
bomDetailList.value = []
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeDetail(res) {
|
||||
const root = res && res.data !== undefined ? res.data : res
|
||||
if (root?.data && typeof root.data === 'object') return root.data
|
||||
if (root && typeof root === 'object') return root
|
||||
return {}
|
||||
}
|
||||
|
||||
function normalizeListData(res) {
|
||||
const root = res && res.data !== undefined ? res.data : res
|
||||
if (Array.isArray(root)) return root
|
||||
if (root?.data && Array.isArray(root.data)) return root.data
|
||||
return []
|
||||
}
|
||||
|
||||
function fieldValue(field) {
|
||||
return textValue(detailData.value ? detailData.value[field] : undefined)
|
||||
}
|
||||
|
||||
function textValue(value) {
|
||||
if (value === 0) return '0'
|
||||
if (value === false) return t('functionCommon.no')
|
||||
if (value === true) return t('functionCommon.yes')
|
||||
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 [y, m, d, hh = 0, mm = 0, ss = 0] = value
|
||||
const pad = (n) => String(n).padStart(2, '0')
|
||||
return `${y}-${pad(m)}-${pad(d)} ${pad(hh)}:${pad(mm)}:${pad(ss)}`
|
||||
}
|
||||
const text = String(value).trim()
|
||||
if (!text) return '-'
|
||||
const numeric = Number(text)
|
||||
if (Number.isFinite(numeric)) {
|
||||
const timestamp = text.length === 10 ? numeric * 1000 : numeric
|
||||
const date = new Date(timestamp)
|
||||
if (!Number.isNaN(date.getTime())) {
|
||||
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')} ${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}:${String(date.getSeconds()).padStart(2, '0')}`
|
||||
}
|
||||
}
|
||||
const date = new Date(text)
|
||||
if (!Number.isNaN(date.getTime())) {
|
||||
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')} ${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}:${String(date.getSeconds()).padStart(2, '0')}`
|
||||
}
|
||||
return text
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page-container { min-height: 100vh; background-color: #f0f2f5; }
|
||||
.fixed-header { position: sticky; top: 0; z-index: 10; }
|
||||
.detail-scroll { height: calc(100vh - 120rpx); }
|
||||
.content-section { padding: 0 24rpx 24rpx; }
|
||||
.info-card { margin-top: 20rpx; background: #ffffff; border-radius: 20rpx; padding: 28rpx; box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05); }
|
||||
.card-title { font-size: 32rpx; color: #1a3a5c; font-weight: 700; margin-bottom: 18rpx; }
|
||||
.info-list { background: #ffffff; }
|
||||
.info-row { display: flex; justify-content: space-between; align-items: flex-start; padding: 18rpx 0; border-bottom: 1rpx solid #edf0f3; }
|
||||
.info-row:last-child { border-bottom: none; }
|
||||
.info-label { font-size: 27rpx; color: #8a9099; width: 220rpx; }
|
||||
.info-value { flex: 1; text-align: right; font-size: 28rpx; color: #30363d; line-height: 1.45; }
|
||||
.remark-row { border-bottom: none; }
|
||||
.remark-value { white-space: pre-wrap; }
|
||||
.text-success { color: #52c41a; }
|
||||
.text-danger { color: #ff4d4f; }
|
||||
.detail-list { margin-top: 10rpx; }
|
||||
.detail-item { background: #f8f9fb; border-radius: 14rpx; padding: 20rpx; margin-bottom: 14rpx; }
|
||||
.detail-item-header { display: flex; align-items: center; gap: 14rpx; margin-bottom: 12rpx; }
|
||||
.detail-index { width: 44rpx; height: 44rpx; border-radius: 22rpx; background: #1a3a5c; color: #fff; font-size: 24rpx; display: flex; align-items: center; justify-content: center; }
|
||||
.detail-product-name { font-size: 28rpx; font-weight: 600; color: #30363d; }
|
||||
.detail-item-body { padding-left: 58rpx; }
|
||||
.detail-row { display: flex; justify-content: space-between; align-items: center; margin-top: 8rpx; }
|
||||
.detail-label { font-size: 24rpx; color: #8a9099; }
|
||||
.detail-value { font-size: 25rpx; color: #30363d; }
|
||||
.detail-empty { text-align: center; color: #909399; padding: 24rpx 0; font-size: 26rpx; }
|
||||
</style>
|
||||
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue