You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
besure_web/src/views/erp/mold/index.vue

494 lines
18 KiB
Vue

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<ContentWrap>
<!-- 列表页 -->
<template v-if="!operateFormVisible && !maintainFormVisible">
<div class="mold-brand-page__header">
<div v-for="card in statusCards" :key="card.key" class="mold-brand-page__stat"
:class="{ 'is-active': currentStatusKey === card.key }" @click="changeStatus(card.key, card.status)">
<div class="mold-brand-page__stat-title">{{ card.label }}</div>
<div class="mold-brand-page__stat-value">{{ card.value }}</div>
</div>
</div>
<el-form ref="queryFormRef" :model="queryParams" :inline="true" class="-mb-15px mt-16px">
<el-form-item prop="keyword">
<el-input v-model="queryParams.keyword" :placeholder="t('MoldManagement.MoldBrandPage.placeholderKeyword')"
clearable class="!w-240px" @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item prop="status">
<el-select v-model="queryParams.status" :placeholder="t('MoldManagement.MoldBrandPage.placeholderStatus')"
clearable class="!w-180px">
<el-option v-for="dict in statusOptions" :key="dict.value" :label="dict.label"
:value="Number(dict.value)" />
</el-select>
</el-form-item>
<el-form-item prop="productIds">
<el-select v-model="queryParams.productIds" multiple :placeholder="t('MoldManagement.MoldBrandPage.placeholderProduct')"
clearable filterable class="!w-180px">
<el-option v-for="product in productOptions" :key="product.id" :label="product.name" :value="product.id" />
</el-select>
</el-form-item>
<el-form-item prop="currentDevice">
<el-select v-model="queryParams.currentDevice"
:placeholder="t('MoldManagement.MoldBrandPage.placeholderDevice')" clearable filterable class="!w-180px">
<el-option v-for="device in deviceOptions" :key="device.id"
:label="`${device.deviceName}${device.deviceCode}`" :value="device.deviceName" />
</el-select>
</el-form-item>
<el-form-item>
<el-button @click="resetQuery">{{ t('MoldManagement.MoldBrandPage.reset') }}</el-button>
<el-button type="primary" @click="handleQuery">{{ t('MoldManagement.MoldBrandPage.query') }}</el-button>
<el-button type="primary" plain @click="openForm('create')" v-hasPermi="['erp:mold-brand:create']">
<Icon icon="ep:plus" class="mr-5px" /> {{ t('MoldManagement.MoldBrandPage.add') }}
</el-button>
<el-button type="success" plain @click="handleExport" :loading="exportLoading"
v-hasPermi="['erp:mold-brand:export']">
<Icon icon="ep:download" class="mr-5px" /> {{ t('MoldManagement.MoldBrandPage.export') }}
</el-button>
</el-form-item>
</el-form>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true" row-key="id"
style="margin-top: 16px;">
<el-table-column :label="t('MoldManagement.MoldBrandPage.image')" align="center" width="120">
<template #default="scope">
<el-image v-if="getImageList(scope.row.images).length" :src="getImageList(scope.row.images)[0]"
:preview-src-list="getImageList(scope.row.images)" fit="cover" preview-teleported
class="mold-brand-page__thumb" />
<span v-else class="mold-brand-page__empty">-</span>
</template>
</el-table-column>
<el-table-column :label="t('MoldManagement.MoldBrandPage.code')" prop="code" min-width="150" sortable>
<template #default="scope">
<span class="mold-brand-page__code-link" @click="previewQrcode(scope.row)">{{ scope.row.code }}</span>
</template>
</el-table-column>
<el-table-column :label="t('MoldManagement.MoldBrandPage.name')" prop="name" min-width="160" sortable />
<el-table-column :label="t('MoldManagement.MoldBrandPage.productName')" prop="productName" min-width="140" />
<el-table-column :label="t('MoldManagement.MoldBrandPage.version')" prop="version" width="100" />
<el-table-column :label="t('MoldManagement.MoldBrandPage.status')" prop="status" width="100" align="center">
<template #default="scope">
<dict-tag :type="DICT_TYPE.ERP_MOLD_STATUS" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column :label="t('MoldManagement.MoldBrandPage.currentDevice')" min-width="140">
<template #default="scope">
{{ scope.row.deviceName }}
</template>
</el-table-column>
<el-table-column :label="t('MoldManagement.MoldBrandPage.moldSize')" prop="moldSize" width="90"
align="center" />
<el-table-column :label="t('MoldManagement.MoldBrandPage.childMoldCount')" prop="childMoldCount" width="90"
align="center" />
<el-table-column :label="t('MoldManagement.MoldBrandPage.createTime')" prop="createTime"
:formatter="dateFormatter" width="160" sortable />
<el-table-column :label="t('MoldManagement.MoldBrandPage.operate')" fixed="right" width="310" align="center">
<template #default="scope">
<el-button link type="primary" @click="openDetail(scope.row.id)">{{ t('MoldManagement.MoldBrandPage.detail')
}}</el-button>
<el-button link type="primary" @click="openOperateForm(1, scope.row)" v-if="scope.row.status === 1">{{
t('MoldManagement.MoldBrandPage.moldUp') }}</el-button>
<el-button link type="primary" @click="openOperateForm(2, scope.row)" v-if="scope.row.status === 0">{{
t('MoldManagement.MoldBrandPage.moldDown') }}</el-button>
<el-button link type="warning" @click="openMaintainForm(scope.row)">{{
t('MoldManagement.MoldBrandPage.maintain') }}</el-button>
<el-button link type="primary" @click="openForm('update', scope.row.id)"
v-hasPermi="['erp:mold-brand:update']">{{ t('MoldManagement.MoldBrandPage.edit') }}</el-button>
<el-button link type="danger" @click="handleDelete(scope.row.id)" v-hasPermi="['erp:mold-brand:delete']">{{
t('MoldManagement.MoldBrandPage.delete') }}</el-button>
</template>
</el-table-column>
</el-table>
<Pagination :total="total" v-model:page="queryParams.pageNo" v-model:limit="queryParams.pageSize"
@pagination="getList" />
</template>
<!-- 上下模操作表单视图 -->
<template v-else-if="operateFormVisible">
<MoldOperateViewComp
ref="moldOperateViewRef"
:mold="currentMold"
:type="operateType"
:device-options="deviceOptions"
@back="closeOperateForm"
@success="onOperateSuccess"
/>
</template>
<!-- 维护操作表单视图 -->
<template v-else-if="maintainFormVisible">
<MoldMaintainViewComp
ref="moldMaintainViewRef"
:mold="currentMold"
:device-options="deviceOptions"
@back="closeMaintainForm"
@success="onMaintainSuccess"
/>
</template>
</ContentWrap>
<Dialog v-model="qrcodeVisible" :title="t('MoldManagement.MoldBrandPage.qrcodeDialogTitle')" width="360px">
<div class="mold-brand-page__qr-card">
<div class="mold-brand-page__qr-title">{{ t('MoldManagement.MoldBrandPage.qrcodeTitle') }}</div>
<QrcodeActionCard :image-url="currentQrcodeRow?.qrCodeUrl" :print-id="currentQrcodeRow?.id" :print-template-type="4"
:print-title="t('MoldManagement.MoldBrandPage.qrcodePrintTitle', { name: currentQrcodeRow?.name || t('MoldManagement.MoldBrandPage.mold') })"
:print-paper-width="80" :print-paper-height="80" :print-max-width="220"
:empty-text="t('MoldManagement.MoldBrandPage.qrcodeEmpty')"
:error-text="t('MoldManagement.MoldBrandPage.qrcodeLoadError')" :refresh-url="getQrcodeRefreshUrl()"
:refresh-disabled="!currentQrcodeRow?.id || !currentQrcodeRow?.code"
:refresh-confirm-text="t('MoldManagement.MoldBrandPage.qrcodeRefreshConfirm')"
@refresh-success="handleQrcodeRefreshSuccess" />
<div class="mold-brand-page__qr-code">{{ currentQrcodeRow?.code || '-' }}</div>
</div>
</Dialog>
<component :is="MoldBrandFormComp" ref="formRef" @success="getList" />
</template>
<script setup lang="ts">
import { defineAsyncComponent } from 'vue'
import { ProductApi, type ProductVO } from '@/api/erp/product/product'
import { MoldBrandApi, type MoldBrandVO } from '@/api/erp/mold'
import { DeviceLedgerApi, type DeviceLedgerVO } from '@/api/mes/deviceledger'
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import download from '@/utils/download'
import QrcodeActionCard from '@/components/QrcodeActionCard/index.vue'
defineOptions({ name: 'MoldBrand' })
const { t } = useI18n()
const message = useMessage()
const { push } = useRouter()
const route = useRoute()
const loading = ref(false)
const exportLoading = ref(false)
const list = ref<MoldBrandVO[]>([])
const total = ref(0)
const productOptions = ref<ProductVO[]>([])
const deviceOptions = ref<DeviceLedgerVO[]>([])
const currentStatusKey = ref('all')
const qrcodeVisible = ref(false)
const currentQrcodeRow = ref<MoldBrandVO | null>(null)
const statusOptions = computed(() => getIntDictOptions(DICT_TYPE.ERP_MOLD_STATUS))
const counters = reactive({
allCount: 0,
onMachineCount: 0,
standbyCount: 0,
repairingCount: 0,
scrappedCount: 0
})
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
keyword: undefined as string | undefined,
code: undefined as string | undefined,
name: undefined as string | undefined,
productIds: [] as number[],
productName: undefined as string | undefined,
status: undefined as number | undefined,
currentDevice: undefined as string | undefined,
currentPosition: undefined as string | undefined
})
const queryFormRef = ref()
const formRef = ref()
const MoldBrandFormComp = defineAsyncComponent(() => import('./MoldBrandForm.vue') as any)
const MoldOperateViewComp = defineAsyncComponent(() => import('./components/MoldOperateView.vue') as any)
const MoldMaintainViewComp = defineAsyncComponent(() => import('./components/MoldMaintainView.vue') as any)
// 上下模操作相关
const operateFormVisible = ref(false)
const operateType = ref(1) // 1: 上模, 2: 下模
const currentMold = ref<MoldBrandVO | null>(null)
const moldOperateViewRef = ref()
// 维护操作相关
const maintainFormVisible = ref(false)
const moldMaintainViewRef = ref()
const statusCards = computed(() => [
{ key: 'all', label: t('MoldManagement.MoldBrandPage.all'), value: counters.allCount, status: undefined },
{ key: 'onMachine', label: t('MoldManagement.MoldBrandPage.onMachine'), value: counters.onMachineCount, status: 1 },
{ key: 'standby', label: t('MoldManagement.MoldBrandPage.standby'), value: counters.standbyCount, status: 0 },
{ key: 'repairing', label: t('MoldManagement.MoldBrandPage.repairing'), value: counters.repairingCount, status: 2 },
{ key: 'scrapped', label: t('MoldManagement.MoldBrandPage.scrapped'), value: counters.scrappedCount, status: 3 }
])
const parseKeyword = () => {
const keyword = queryParams.keyword?.trim()
queryParams.code = keyword || undefined
queryParams.name = keyword || undefined
}
const getImageList = (images?: string) => {
if (!images) return []
return String(images)
.split(',')
.map((item) => item.trim())
.filter(Boolean)
}
const normalizePageResult = (data: any) => {
const pageResult = data?.pageResult ?? data ?? {}
list.value = pageResult.list ?? []
total.value = pageResult.total ?? 0
counters.allCount = Number(data?.allCount ?? pageResult.total ?? 0)
counters.onMachineCount = Number(data?.onMachineCount ?? 0)
counters.standbyCount = Number(data?.standbyCount ?? 0)
counters.repairingCount = Number(data?.repairingCount ?? 0)
counters.scrappedCount = Number(data?.scrappedCount ?? 0)
}
const getList = async () => {
loading.value = true
try {
parseKeyword()
const selected = productOptions.value.filter((item) => (queryParams.productIds || []).includes(item.id))
queryParams.productName = selected.map((p) => p.name).join(',')
const data = await MoldBrandApi.getMoldBrandPage({
...queryParams,
currentDevice: queryParams.currentDevice
})
normalizePageResult(data)
} finally {
loading.value = false
}
}
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
const resetQuery = () => {
queryFormRef.value?.resetFields()
currentStatusKey.value = 'all'
queryParams.status = undefined
queryParams.productIds = []
queryParams.productName = undefined
handleQuery()
}
const changeStatus = (key: string, status?: number) => {
currentStatusKey.value = key
queryParams.status = status
handleQuery()
}
const openForm = (type: string, id?: number) => {
formRef.value.open(type, id)
}
const handleDelete = async (id: number) => {
try {
await message.delConfirm()
await MoldBrandApi.deleteMoldBrand(id)
message.success(t('common.delSuccess'))
await getList()
} catch { }
}
const handleExport = async () => {
try {
await message.exportConfirm()
exportLoading.value = true
parseKeyword()
const data = await MoldBrandApi.exportMoldBrand(queryParams)
download.excel(data, t('MoldManagement.MoldBrandPage.exportFilename'))
} finally {
exportLoading.value = false
}
}
const handleReservedAction = (action: string) => {
message.info(t('MoldManagement.MoldBrandPage.reservedAction', { action }))
}
const previewQrcode = (row: MoldBrandVO) => {
currentQrcodeRow.value = row
qrcodeVisible.value = true
}
const getQrcodeRefreshUrl = () => {
if (!currentQrcodeRow.value?.id || !currentQrcodeRow.value?.code) return ''
return `/erp/mold-brand/regenerate-code?id=${currentQrcodeRow.value.id}&code=${encodeURIComponent(String(currentQrcodeRow.value.code))}`
}
const handleQrcodeRefreshSuccess = async (data: any) => {
if (!currentQrcodeRow.value?.id) return
if (data?.qrcodeUrl) {
currentQrcodeRow.value.qrCodeUrl = data.qrcodeUrl
return
}
const moldData = await MoldBrandApi.getMoldBrand(currentQrcodeRow.value.id)
currentQrcodeRow.value.qrCodeUrl = moldData?.qrCodeUrl
currentQrcodeRow.value.code = moldData?.code ?? currentQrcodeRow.value.code
}
const openDetail = (id: number) => {
push({ name: 'ErpMoldBrandDetail', params: { id } })
}
// 上下模操作函数
const openOperateForm = async (type: number, row: MoldBrandVO) => {
operateType.value = type
currentMold.value = row
operateFormVisible.value = true
await nextTick()
moldOperateViewRef.value?.open()
}
const closeOperateForm = () => {
operateFormVisible.value = false
currentMold.value = null
}
const onOperateSuccess = () => {
// 提交成功后的回调
}
// 维护操作函数
const openMaintainForm = async (row: MoldBrandVO) => {
currentMold.value = row
maintainFormVisible.value = true
await nextTick()
moldMaintainViewRef.value?.open()
}
const closeMaintainForm = () => {
maintainFormVisible.value = false
currentMold.value = null
}
const onMaintainSuccess = () => {
// 维护提交成功后的回调
}
onMounted(async () => {
productOptions.value = await ProductApi.getMesProductSimpleList()
deviceOptions.value = await DeviceLedgerApi.getDeviceLedgerList()
await getList()
// 从详情页跳转过来,自动打开上下模操作
const { operateType: queryOperateType, moldId: queryMoldId } = route.query
if (queryOperateType && queryMoldId) {
const moldRow = list.value.find((item) => item.id === Number(queryMoldId))
if (moldRow) {
await openOperateForm(Number(queryOperateType), moldRow)
}
// 清除 query 参数,避免刷新重复触发
push({ path: route.path })
}
})
</script>
<style scoped>
.mold-brand-page__header {
display: grid;
grid-template-columns: repeat(5, 10vw);
gap: 12px;
}
.mold-brand-page__stat {
width: 10vw;
padding: 16px 18px;
border: 1px solid var(--el-border-color-light);
border-radius: 12px;
cursor: pointer;
background: linear-gradient(180deg, #fff 0%, #f8fbff 100%);
transition: all 0.2s ease;
}
.mold-brand-page__stat.is-active {
border-color: var(--el-color-primary);
box-shadow: 0 10px 24px rgba(64, 158, 255, 0.12);
}
.mold-brand-page__stat-title {
color: var(--el-text-color-regular);
font-size: 14px;
}
.mold-brand-page__stat-value {
margin-top: 8px;
color: var(--el-text-color-primary);
font-size: 28px;
font-weight: 700;
line-height: 1;
}
.mold-brand-page__thumb {
width: 56px;
height: 56px;
border-radius: 8px;
border: 1px solid var(--el-border-color-lighter);
}
.mold-brand-page__empty {
display: inline-flex;
align-items: center;
justify-content: center;
width: 56px;
height: 56px;
border-radius: 8px;
border: 1px solid var(--el-border-color-lighter);
color: var(--el-text-color-placeholder);
}
.mold-brand-page__code-link {
color: var(--el-color-primary);
cursor: pointer;
text-decoration: underline;
}
.mold-brand-page__code-link:hover {
color: var(--el-color-primary-light-3);
}
.mold-brand-page__qrcode-wrap {
display: flex;
justify-content: center;
}
.mold-brand-page__qrcode {
width: 220px;
height: 220px;
}
.mold-brand-page__qr-card {
display: flex;
flex-direction: column;
align-items: center;
padding: 16px;
border: 1px solid var(--el-border-color-lighter);
border-radius: 16px;
background: #fff;
}
.mold-brand-page__qr-title {
margin-bottom: 12px;
color: var(--el-text-color-primary);
font-weight: 600;
text-align: center;
}
.mold-brand-page__qr-code {
margin-top: 12px;
text-align: center;
font-weight: 600;
}
@media (max-width: 1200px) {
.mold-brand-page__header {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@media (max-width: 768px) {
.mold-brand-page__header {
grid-template-columns: 1fr;
}
}
</style>