feat(production-master-data): add 异常不良管理功能
1. 新增设备类别API接口封装 2. 新增异常不良管理的CRUD、导入导出API 3. 添加异常不良管理页面路由与多语言配置 4. 新增文件工具类支持Excel读写下载 5. 实现完整的异常不良管理页面与导入弹窗 6. 新增功能测试流程文档 7. 安装xlsx依赖支持Excel操作
This commit is contained in:
19
src/api/production-master-data/device-category.js
Normal file
19
src/api/production-master-data/device-category.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import { request } from '@/api/_service'
|
||||
|
||||
const BASE = 'production_configuration/device_model/device_category/'
|
||||
|
||||
function apiParams (method, data = {}) {
|
||||
return {
|
||||
method: `production_configuration_device_model_device_category_${method}`,
|
||||
platform: 'background',
|
||||
...data
|
||||
}
|
||||
}
|
||||
|
||||
export function getDeviceCategoryAll (data) {
|
||||
return request({
|
||||
url: BASE + 'all',
|
||||
method: 'get',
|
||||
params: apiParams('all', data)
|
||||
})
|
||||
}
|
||||
76
src/api/production-master-data/product-ng-info.js
Normal file
76
src/api/production-master-data/product-ng-info.js
Normal file
@@ -0,0 +1,76 @@
|
||||
import { request } from '@/api/_service'
|
||||
|
||||
const BASE = 'production_configuration/product_model/product_ng_info/'
|
||||
|
||||
function apiParams (method, data = {}) {
|
||||
return {
|
||||
method: `production_configuration_product_model_product_ng_info_${method}`,
|
||||
platform: 'background',
|
||||
...data
|
||||
}
|
||||
}
|
||||
|
||||
export function getProductNgInfoAll (data) {
|
||||
return request({
|
||||
url: BASE + 'all',
|
||||
method: 'get',
|
||||
params: { ...data }
|
||||
})
|
||||
}
|
||||
|
||||
export function getProductNgInfoList (data) {
|
||||
return request({
|
||||
url: BASE + 'list',
|
||||
method: 'get',
|
||||
params: { ...data }
|
||||
})
|
||||
}
|
||||
|
||||
export function createProductNgInfo (data) {
|
||||
return request({
|
||||
url: BASE + 'create',
|
||||
method: 'post',
|
||||
data: apiParams('create', data)
|
||||
})
|
||||
}
|
||||
|
||||
export function editProductNgInfo (data) {
|
||||
return request({
|
||||
url: BASE + 'edit',
|
||||
method: 'put',
|
||||
data: apiParams('edit', data)
|
||||
})
|
||||
}
|
||||
|
||||
export function deleteProductNgInfo (data) {
|
||||
return request({
|
||||
url: BASE + 'delete',
|
||||
method: 'delete',
|
||||
data: apiParams('delete', data)
|
||||
})
|
||||
}
|
||||
|
||||
export function getImportTemplate (data) {
|
||||
return request({
|
||||
url: BASE + 'get_import_template',
|
||||
method: 'post',
|
||||
responseType: 'blob',
|
||||
data: apiParams('get_import_template', data)
|
||||
})
|
||||
}
|
||||
|
||||
export function productNgInfoImport (data) {
|
||||
return request({
|
||||
url: BASE + 'import',
|
||||
method: 'post',
|
||||
data: apiParams('import', data)
|
||||
})
|
||||
}
|
||||
|
||||
export function productNgInfoExportTask (data) {
|
||||
return request({
|
||||
url: BASE + 'product_ng_info_export_task',
|
||||
method: 'post',
|
||||
data: apiParams('product_ng_info_export_task', data)
|
||||
})
|
||||
}
|
||||
@@ -248,6 +248,60 @@
|
||||
"help": "Unit is used to maintain material measurement units (e.g. piece, box, kg)"
|
||||
}
|
||||
},
|
||||
"product_model": {
|
||||
"product_ng_info": {
|
||||
"search": "Search",
|
||||
"reset": "Reset",
|
||||
"device_category": "Device Category",
|
||||
"select_device_category": "Please select device category",
|
||||
"query_type": "Query Type",
|
||||
"select_query_type": "Please select query type",
|
||||
"type_error": "Error",
|
||||
"type_ng": "NG",
|
||||
"ng_code": "Error/NG Code",
|
||||
"enter_ng_code": "Please enter error/NG code",
|
||||
"ng_name": "Error/NG Name",
|
||||
"enter_ng_name": "Please enter error/NG name",
|
||||
"type": "Category",
|
||||
"select_type": "Please select category",
|
||||
"exception_ng_category": "Error/NG Category",
|
||||
"remark": "Remark",
|
||||
"enter_remark": "Please enter remark",
|
||||
"remark_length": "Length 1 to 100 characters",
|
||||
"operation": "Action",
|
||||
"add": "Add",
|
||||
"edit": "Edit",
|
||||
"delete": "Delete",
|
||||
"batch_delete": "Batch Delete",
|
||||
"import": "Import",
|
||||
"export": "Export",
|
||||
"operation_success": "Operation succeeded",
|
||||
"add_exception_ng_category": "Add Error/NG Category",
|
||||
"edit_exception_ng_category": "Edit Error/NG Category",
|
||||
"cancel": "Cancel",
|
||||
"confirm": "Confirm",
|
||||
"prompt": "Prompt",
|
||||
"confirm_delete": "Are you sure to perform this operation?",
|
||||
"confirm_batch_delete": "Are you sure to delete the selected categories?",
|
||||
"please_select_data": "Please select data first",
|
||||
"validation_fail": "Validation failed",
|
||||
"export_confirm_tip": "Are you sure to export the current query results?",
|
||||
"operation_cancelled": "Operation cancelled",
|
||||
"create_download_task_success": "Export task created successfully",
|
||||
"import_exception_ng_data": "Import Error/NG Data",
|
||||
"import_file_format_tip": "Please import file in template format",
|
||||
"import_table": "Import Table",
|
||||
"select_file": "Select File",
|
||||
"download_template": "Download Template",
|
||||
"preview": "Preview",
|
||||
"import_template_name": "Error/NG data import template",
|
||||
"file_not_exist": "File does not exist",
|
||||
"upload_format_error": "Upload file format error",
|
||||
"file_column_missing": "File column missing: {title}",
|
||||
"please_import_data": "Please import data first",
|
||||
"help": "Error/NG management is used to maintain equipment error types and product NG types"
|
||||
}
|
||||
},
|
||||
"product_management": {
|
||||
"product_list": {
|
||||
"search": "Search",
|
||||
|
||||
@@ -248,6 +248,60 @@
|
||||
"help": "计量单位用于维护物料所用单位(如个、箱、kg)"
|
||||
}
|
||||
},
|
||||
"product_model": {
|
||||
"product_ng_info": {
|
||||
"search": "查询",
|
||||
"reset": "重置",
|
||||
"device_category": "设备类别",
|
||||
"select_device_category": "请选择设备类别",
|
||||
"query_type": "查询类型",
|
||||
"select_query_type": "请选择查询类型",
|
||||
"type_error": "异常",
|
||||
"type_ng": "不良",
|
||||
"ng_code": "异常不良编码",
|
||||
"enter_ng_code": "请输入异常不良编码",
|
||||
"ng_name": "异常不良名称",
|
||||
"enter_ng_name": "请输入异常不良名称",
|
||||
"type": "类别",
|
||||
"select_type": "请选择类别",
|
||||
"exception_ng_category": "异常不良类别",
|
||||
"remark": "备注",
|
||||
"enter_remark": "请输入备注",
|
||||
"remark_length": "长度在1到100个字符",
|
||||
"operation": "操作",
|
||||
"add": "新 增",
|
||||
"edit": "编 辑",
|
||||
"delete": "删 除",
|
||||
"batch_delete": "批量删除",
|
||||
"import": "导 入",
|
||||
"export": "导 出",
|
||||
"operation_success": "操作成功",
|
||||
"add_exception_ng_category": "新增异常不良类别",
|
||||
"edit_exception_ng_category": "编辑异常不良类别",
|
||||
"cancel": "取消",
|
||||
"confirm": "确定",
|
||||
"prompt": "提示",
|
||||
"confirm_delete": "确定要执行该操作吗?",
|
||||
"confirm_batch_delete": "确定要删除所选异常不良类别吗?",
|
||||
"please_select_data": "请先选择数据",
|
||||
"validation_fail": "校验失败",
|
||||
"export_confirm_tip": "确定要导出当前查询结果吗?",
|
||||
"operation_cancelled": "操作已取消",
|
||||
"create_download_task_success": "创建导出任务成功",
|
||||
"import_exception_ng_data": "导入异常不良数据",
|
||||
"import_file_format_tip": "请按模板格式导入文件",
|
||||
"import_table": "导入列表",
|
||||
"select_file": "选择文件",
|
||||
"download_template": "下载模板",
|
||||
"preview": "预览",
|
||||
"import_template_name": "异常不良数据导入模版",
|
||||
"file_not_exist": "文件不存在",
|
||||
"upload_format_error": "上传文件格式错误",
|
||||
"file_column_missing": "文件列缺失: {title}",
|
||||
"please_import_data": "请先导入数据",
|
||||
"help": "异常不良管理用于维护设备的异常种类和产品的不良种类信息"
|
||||
}
|
||||
},
|
||||
"product_management": {
|
||||
"product_list": {
|
||||
"search": "查询",
|
||||
|
||||
@@ -61,6 +61,12 @@ export default {
|
||||
name: `${pre}material_model-material_unit`,
|
||||
meta: { ...meta, cache: true, title: '计量单位' },
|
||||
component: _import('production-master-data/material-model/material-unit')
|
||||
},
|
||||
{
|
||||
path: 'product_model/product_ng_info',
|
||||
name: `${pre}product_model-product_ng_info`,
|
||||
meta: { ...meta, cache: true, title: '异常不良管理' },
|
||||
component: _import('production-master-data/product-model/product-ng-info')
|
||||
}
|
||||
])('production_configuration-')
|
||||
}
|
||||
|
||||
41
src/utils/file.js
Normal file
41
src/utils/file.js
Normal file
@@ -0,0 +1,41 @@
|
||||
import * as XLSX from 'xlsx'
|
||||
|
||||
export function downloadRename (blob, fileType, filename) {
|
||||
const typesMap = {
|
||||
xlsx: 'application/vnd.ms-excel',
|
||||
xls: 'application/vnd.ms-excel',
|
||||
pdf: 'application/pdf',
|
||||
doc: 'application/msword',
|
||||
docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||
csv: 'text/csv'
|
||||
}
|
||||
const type = typesMap[fileType] || ''
|
||||
const newBlob = new Blob([blob], { type })
|
||||
const href = URL.createObjectURL(newBlob)
|
||||
const a = document.createElement('a')
|
||||
a.href = href
|
||||
a.download = filename + '.' + fileType
|
||||
document.body.appendChild(a)
|
||||
a.click()
|
||||
document.body.removeChild(a)
|
||||
URL.revokeObjectURL(href)
|
||||
}
|
||||
|
||||
export function readExcel (file) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader()
|
||||
reader.readAsBinaryString(file)
|
||||
reader.onload = ev => {
|
||||
try {
|
||||
const data = ev.target.result
|
||||
const workbook = XLSX.read(data, { type: 'binary' })
|
||||
const firstSheet = workbook.Sheets[workbook.SheetNames[0]]
|
||||
const list = XLSX.utils.sheet_to_json(firstSheet)
|
||||
resolve(list)
|
||||
} catch (e) {
|
||||
reject(e)
|
||||
}
|
||||
}
|
||||
reader.onerror = () => reject(new Error('文件读取失败'))
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
:visible.sync="visibleProxy"
|
||||
:title="$t(key('import_exception_ng_data'))"
|
||||
:width="width"
|
||||
:close-on-click-modal="false"
|
||||
@close="onClose"
|
||||
>
|
||||
<div>
|
||||
<el-alert
|
||||
:title="$t(key('import_file_format_tip'))"
|
||||
:closable="false"
|
||||
type="warning"
|
||||
/>
|
||||
|
||||
<el-form ref="form" label-width="100px" style="margin-top:10px">
|
||||
<el-form-item :label="$t(key('import_table'))">
|
||||
<el-upload
|
||||
action=""
|
||||
:multiple="false"
|
||||
:show-file-list="true"
|
||||
:file-list="importFileList"
|
||||
accept=".xls,.xlsx"
|
||||
:http-request="onUpload"
|
||||
>
|
||||
<el-button size="mini" type="success">
|
||||
{{ $t(key('select_file')) }}
|
||||
</el-button>
|
||||
<el-button
|
||||
style="margin-left:10px"
|
||||
size="mini"
|
||||
type="primary"
|
||||
:loading="downloadLoading"
|
||||
@click="onDownloadTemplate"
|
||||
>
|
||||
{{ $t(key('download_template')) }}
|
||||
</el-button>
|
||||
</el-upload>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item :label="$t(key('preview'))">
|
||||
<el-table
|
||||
v-loading="previewLoading"
|
||||
:data="previewList"
|
||||
height="350"
|
||||
border
|
||||
size="mini"
|
||||
>
|
||||
<el-table-column type="selection" width="55" />
|
||||
<el-table-column :label="$t(key('device_category'))" prop="device_category_name" min-width="100" />
|
||||
<el-table-column :label="$t(key('exception_ng_category'))" prop="type" min-width="100" />
|
||||
<el-table-column :label="$t(key('ng_code'))" prop="number" min-width="100" />
|
||||
<el-table-column :label="$t(key('ng_name'))" prop="explain" min-width="120" />
|
||||
<el-table-column :label="$t(key('remark'))" prop="note" min-width="120" />
|
||||
</el-table>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button size="mini" @click="visibleProxy = false">
|
||||
{{ $t(key('cancel')) }}
|
||||
</el-button>
|
||||
<el-button type="primary" size="mini" :loading="submitting" @click="onSubmit">
|
||||
{{ $t(key('confirm')) }}
|
||||
</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { i18nMixin } from '@/composables/useI18n'
|
||||
import { getImportTemplate, productNgInfoImport } from '@/api/production-master-data/product-ng-info'
|
||||
import { downloadRename, readExcel } from '@/utils/file'
|
||||
|
||||
const EXPECTED_COLUMNS = ['设备类别', '异常不良类别', '异常不良编码', '异常不良名称', '备注']
|
||||
|
||||
export default {
|
||||
name: 'ProductNgInfoImportDialog',
|
||||
mixins: [i18nMixin('page.production_master_data.product_model.product_ng_info')],
|
||||
props: {
|
||||
visible: Boolean,
|
||||
width: { type: String, default: '50%' }
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
submitting: false,
|
||||
downloadLoading: false,
|
||||
previewLoading: false,
|
||||
importFileList: [],
|
||||
previewList: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
visibleProxy: {
|
||||
get () { return this.visible },
|
||||
set (val) { this.$emit('update:visible', val) }
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
visible (val) {
|
||||
if (val) this.reset()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
reset () {
|
||||
this.importFileList = []
|
||||
this.previewList = []
|
||||
this.submitting = false
|
||||
this.previewLoading = false
|
||||
},
|
||||
onDownloadTemplate () {
|
||||
this.downloadLoading = true
|
||||
getImportTemplate({})
|
||||
.then(res => { downloadRename(res, 'xlsx', this.key('import_template_name')) })
|
||||
.finally(() => { this.downloadLoading = false })
|
||||
},
|
||||
onUpload (e) {
|
||||
const file = e.file
|
||||
if (!file) {
|
||||
this.$message.error(this.$t(this.key('file_not_exist')))
|
||||
return
|
||||
}
|
||||
const lower = file.name.toLowerCase()
|
||||
if (!/\.(xls|xlsx)$/.test(lower)) {
|
||||
this.$message.error(this.$t(this.key('upload_format_error')))
|
||||
return
|
||||
}
|
||||
this.importFileList = [file]
|
||||
this.previewLoading = true
|
||||
readExcel(file)
|
||||
.then(res => {
|
||||
if (!res || !res.length) return
|
||||
const firstRow = res[0]
|
||||
for (const col of EXPECTED_COLUMNS) {
|
||||
if (!Object.prototype.hasOwnProperty.call(firstRow, col)) {
|
||||
this.$message.error(this.$t(this.key('file_column_missing'), { title: col }))
|
||||
return
|
||||
}
|
||||
}
|
||||
this.previewList = res.map(row => ({
|
||||
device_category_name: row['设备类别'],
|
||||
type: row['异常不良类别'],
|
||||
number: row['异常不良编码'],
|
||||
explain: row['异常不良名称'],
|
||||
note: row['备注']
|
||||
}))
|
||||
})
|
||||
.catch(err => {
|
||||
this.$message.error(err.message || this.$t(this.key('upload_format_error')))
|
||||
})
|
||||
.finally(() => { this.previewLoading = false })
|
||||
},
|
||||
onSubmit () {
|
||||
if (!this.previewList.length) {
|
||||
this.$message.error(this.$t(this.key('please_import_data')))
|
||||
return
|
||||
}
|
||||
this.submitting = true
|
||||
productNgInfoImport({
|
||||
import_data: JSON.stringify(this.previewList)
|
||||
})
|
||||
.then(() => {
|
||||
this.$message.success(this.$t(this.key('operation_success')))
|
||||
this.visibleProxy = false
|
||||
this.$emit('saved')
|
||||
})
|
||||
.finally(() => { this.submitting = false })
|
||||
},
|
||||
onClose () {
|
||||
this.reset()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,459 @@
|
||||
<template>
|
||||
<d2-container>
|
||||
<template #header>
|
||||
<div class="search-bar">
|
||||
<el-form :inline="true" size="mini">
|
||||
<el-form-item :label="$t(key('device_category'))">
|
||||
<el-select
|
||||
v-model="search.device_category_id"
|
||||
:placeholder="$t(key('select_device_category'))"
|
||||
clearable
|
||||
filterable
|
||||
style="width:200px"
|
||||
@focus="loadDeviceCategories"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in deviceCategoryOptions"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t(key('query_type'))">
|
||||
<el-select
|
||||
v-model="search.type"
|
||||
:placeholder="$t(key('select_query_type'))"
|
||||
clearable
|
||||
style="width:200px"
|
||||
>
|
||||
<el-option :label="$t(key('type_error'))" value="ERR" />
|
||||
<el-option :label="$t(key('type_ng'))" value="NG" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t(key('ng_code'))">
|
||||
<el-input
|
||||
v-model="search.number"
|
||||
:placeholder="$t(key('enter_ng_code'))"
|
||||
clearable
|
||||
style="width:200px"
|
||||
@keyup.enter.native="onSearch"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t(key('ng_name'))">
|
||||
<el-input
|
||||
v-model="search.explain"
|
||||
:placeholder="$t(key('enter_ng_name'))"
|
||||
clearable
|
||||
style="width:200px"
|
||||
@keyup.enter.native="onSearch"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="el-icon-search" @click="onSearch">
|
||||
{{ $t(key('search')) }}
|
||||
</el-button>
|
||||
<el-button icon="el-icon-refresh" @click="onReset">
|
||||
{{ $t(key('reset')) }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<page-table
|
||||
ref="pageTable"
|
||||
:columns="columns"
|
||||
:data="tableData"
|
||||
:loading="loading"
|
||||
:toolbar-buttons="toolbarButtons"
|
||||
:row-buttons="rowButtons"
|
||||
:pagination="pagination"
|
||||
help-url="/help/product-ng-info"
|
||||
:help-text="$t(ckey('help'))"
|
||||
auto-height
|
||||
@page-change="onPageChange"
|
||||
@selection-change="onSelect"
|
||||
/>
|
||||
|
||||
<page-dialog-form
|
||||
ref="dialogForm"
|
||||
:visible.sync="dialogVisible"
|
||||
:title="dialogTitle"
|
||||
:width="'35%'"
|
||||
:form-cols="formCols"
|
||||
:form-data="formData"
|
||||
:rules="rules"
|
||||
:label-width="'120px'"
|
||||
:submitting="submitting"
|
||||
:confirm-text="key('confirm')"
|
||||
:cancel-text="key('cancel')"
|
||||
@submit="onDialogSubmit"
|
||||
@close="onDialogClose"
|
||||
/>
|
||||
|
||||
<product-ng-info-import-dialog
|
||||
:visible.sync="importVisible"
|
||||
@saved="onImportSaved"
|
||||
/>
|
||||
</d2-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { useTableColumns } from '@/composables/useTableColumns'
|
||||
import { useTableButtons } from '@/composables/useTableButtons'
|
||||
import { i18nMixin } from '@/composables/useI18n'
|
||||
import { confirmMixin } from '@/composables/useConfirmHandle'
|
||||
import {
|
||||
getProductNgInfoList,
|
||||
createProductNgInfo,
|
||||
editProductNgInfo,
|
||||
deleteProductNgInfo,
|
||||
productNgInfoExportTask
|
||||
} from '@/api/production-master-data/product-ng-info'
|
||||
import { getDeviceCategoryAll } from '@/api/production-master-data/device-category'
|
||||
import PageTable from '@/components/page-table'
|
||||
import PageDialogForm from '@/components/page-dialog-form'
|
||||
import ProductNgInfoImportDialog from './components/ImportDialog/index.vue'
|
||||
|
||||
export default {
|
||||
name: 'production-master-data-product-ng-info',
|
||||
components: { PageTable, PageDialogForm, ProductNgInfoImportDialog },
|
||||
mixins: [i18nMixin('page.production_master_data.product_model.product_ng_info'), confirmMixin],
|
||||
data () {
|
||||
return {
|
||||
loading: false,
|
||||
submitting: false,
|
||||
tableData: [],
|
||||
selectedRows: [],
|
||||
dialogVisible: false,
|
||||
dialogTitle: '',
|
||||
editId: null,
|
||||
handleType: 'create',
|
||||
importVisible: false,
|
||||
search: { device_category_id: '', type: '', number: '', explain: '' },
|
||||
pagination: { current: 1, size: 10, total: 0 },
|
||||
deviceCategoryOptions: [],
|
||||
formData: { device_category_id: '', type: '', number: '', explain: '', note: '' },
|
||||
rules: {
|
||||
device_category_id: [
|
||||
{ required: true, message: this.key('select_device_category'), trigger: 'change' }
|
||||
],
|
||||
type: [
|
||||
{ required: true, message: this.key('select_type'), trigger: 'change' }
|
||||
],
|
||||
number: [
|
||||
{ required: true, message: this.key('enter_ng_code'), trigger: 'blur' },
|
||||
{ min: 1, max: 100, message: this.key('remark_length'), trigger: 'blur' }
|
||||
],
|
||||
explain: [
|
||||
{ required: true, message: this.key('enter_ng_name'), trigger: 'blur' },
|
||||
{ min: 1, max: 100, message: this.key('remark_length'), trigger: 'blur' }
|
||||
]
|
||||
},
|
||||
formCols: [],
|
||||
columns: [],
|
||||
toolbarButtons: [],
|
||||
rowButtons: []
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.formCols = [
|
||||
[
|
||||
{
|
||||
type: 'select',
|
||||
prop: 'device_category_id',
|
||||
label: this.key('device_category'),
|
||||
placeholder: this.key('select_device_category'),
|
||||
clearable: true,
|
||||
style: { width: '90%' },
|
||||
options: [],
|
||||
keys: { label: 'name', value: 'id' },
|
||||
onFocus: this.onFormDeviceCategoryFocus
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
type: 'select',
|
||||
prop: 'type',
|
||||
label: this.key('type'),
|
||||
placeholder: this.key('select_type'),
|
||||
clearable: true,
|
||||
style: { width: '90%' },
|
||||
options: [
|
||||
{ name: this.key('type_error'), code: 'ERR' },
|
||||
{ name: this.key('type_ng'), code: 'NG' }
|
||||
],
|
||||
keys: { label: 'name', value: 'code' }
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
type: 'input',
|
||||
prop: 'number',
|
||||
label: this.key('ng_code'),
|
||||
placeholder: this.key('enter_ng_code'),
|
||||
clearable: true,
|
||||
style: { width: '90%' }
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
type: 'input',
|
||||
prop: 'explain',
|
||||
label: this.key('ng_name'),
|
||||
placeholder: this.key('enter_ng_name'),
|
||||
clearable: true,
|
||||
style: { width: '90%' }
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
type: 'input',
|
||||
inputType: 'textarea',
|
||||
prop: 'note',
|
||||
label: this.key('remark'),
|
||||
placeholder: this.key('enter_remark'),
|
||||
autosize: { minRows: 2, maxRows: 6 },
|
||||
clearable: true,
|
||||
style: { width: '90%' }
|
||||
}
|
||||
]
|
||||
]
|
||||
|
||||
this.columns = useTableColumns([
|
||||
{ type: 'selection', width: 55 },
|
||||
{ prop: 'device_category_name', label: this.key('device_category'), minWidth: 140 },
|
||||
{ prop: 'type', label: this.key('exception_ng_category'), minWidth: 120 },
|
||||
{ prop: 'number', label: this.key('ng_code'), minWidth: 140 },
|
||||
{ prop: 'explain', label: this.key('ng_name'), minWidth: 140 },
|
||||
{ prop: 'note', label: this.key('remark'), minWidth: 200 },
|
||||
{ prop: '_actions', label: this.key('operation'), width: 180, fixed: 'right' }
|
||||
])
|
||||
|
||||
const btns = useTableButtons({
|
||||
toolbar: [
|
||||
{
|
||||
key: 'add',
|
||||
label: this.key('add'),
|
||||
icon: 'el-icon-plus',
|
||||
type: 'primary',
|
||||
auth: '/production_configuration/product_model/product_ng_info/create',
|
||||
onClick: this.openAdd
|
||||
},
|
||||
{
|
||||
key: 'batch-delete',
|
||||
label: this.key('batch_delete'),
|
||||
icon: 'el-icon-delete',
|
||||
type: 'danger',
|
||||
auth: '/production_configuration/product_model/product_ng_info/delete',
|
||||
onClick: this.handleBatchDelete
|
||||
},
|
||||
{
|
||||
key: 'import',
|
||||
label: this.key('import'),
|
||||
icon: 'el-icon-upload2',
|
||||
color: '#3CBA92',
|
||||
auth: '/production_configuration/product_model/product_ng_info/create',
|
||||
onClick: this.openImport
|
||||
},
|
||||
{
|
||||
key: 'export',
|
||||
label: this.key('export'),
|
||||
icon: 'el-icon-download',
|
||||
color: '#35C2EE',
|
||||
auth: '/production_configuration/product_model/product_ng_info/export',
|
||||
onClick: this.handleExport
|
||||
}
|
||||
],
|
||||
row: [
|
||||
{
|
||||
key: 'edit',
|
||||
label: this.key('edit'),
|
||||
icon: 'el-icon-edit',
|
||||
auth: '/production_configuration/product_model/product_ng_info/edit',
|
||||
onClick: this.openEdit
|
||||
},
|
||||
{
|
||||
key: 'delete',
|
||||
label: this.key('delete'),
|
||||
icon: 'el-icon-delete',
|
||||
color: 'danger',
|
||||
auth: '/production_configuration/product_model/product_ng_info/delete',
|
||||
onClick: this.handleDelete
|
||||
}
|
||||
]
|
||||
}, this.$permission)
|
||||
this.toolbarButtons = btns.toolbarButtons
|
||||
this.rowButtons = btns.rowButtons
|
||||
this.fetchData()
|
||||
},
|
||||
methods: {
|
||||
async fetchData () {
|
||||
this.loading = true
|
||||
try {
|
||||
const res = await getProductNgInfoList({
|
||||
...this.search,
|
||||
page_no: this.pagination.current,
|
||||
page_size: this.pagination.size
|
||||
})
|
||||
const data = Array.isArray(res) ? res : (res.data || {})
|
||||
const list = Array.isArray(data) ? data : (data.data || [])
|
||||
const total = Array.isArray(data) ? data.length : (data.count || 0)
|
||||
this.tableData = list
|
||||
this.pagination.total = total
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
onSearch () {
|
||||
this.pagination.current = 1
|
||||
this.fetchData()
|
||||
},
|
||||
onReset () {
|
||||
this.search = { device_category_id: '', type: '', number: '', explain: '' }
|
||||
this.pagination.current = 1
|
||||
this.fetchData()
|
||||
},
|
||||
onPageChange (page) {
|
||||
this.pagination.current = page.current
|
||||
this.pagination.size = page.size
|
||||
this.fetchData()
|
||||
},
|
||||
onSelect (rows) {
|
||||
this.selectedRows = rows
|
||||
},
|
||||
async loadDeviceCategories () {
|
||||
if (this.deviceCategoryOptions.length) return
|
||||
try {
|
||||
const res = await getDeviceCategoryAll({})
|
||||
const list = Array.isArray(res) ? res : (res.data || [])
|
||||
this.deviceCategoryOptions = Array.isArray(list) ? list : []
|
||||
this.searchFormDeviceCategoryUpdate()
|
||||
} catch { /* ignore */ }
|
||||
},
|
||||
searchFormDeviceCategoryUpdate () {
|
||||
const catCol = this.formCols[0] && this.formCols[0][0]
|
||||
if (catCol && catCol.type === 'select') {
|
||||
catCol.options = this.deviceCategoryOptions
|
||||
}
|
||||
},
|
||||
onFormDeviceCategoryFocus () {
|
||||
this.loadDeviceCategories()
|
||||
},
|
||||
resetForm () {
|
||||
this.formData = { device_category_id: '', type: '', number: '', explain: '', note: '' }
|
||||
this.editId = null
|
||||
this.searchFormDeviceCategoryUpdate()
|
||||
},
|
||||
openAdd () {
|
||||
this.handleType = 'create'
|
||||
this.dialogTitle = this.key('add_exception_ng_category')
|
||||
this.loadDeviceCategories()
|
||||
this.$nextTick(() => {
|
||||
this.$refs.dialogForm && this.$refs.dialogForm.reset()
|
||||
this.resetForm()
|
||||
this.dialogVisible = true
|
||||
})
|
||||
},
|
||||
openEdit (row) {
|
||||
this.handleType = 'edit'
|
||||
this.dialogTitle = this.key('edit_exception_ng_category')
|
||||
this.editId = row.id
|
||||
this.loadDeviceCategories()
|
||||
this.formData = {
|
||||
device_category_id: row.device_category_id || '',
|
||||
type: row.type || '',
|
||||
number: row.number || '',
|
||||
explain: row.explain || '',
|
||||
note: row.note || ''
|
||||
}
|
||||
this.dialogVisible = true
|
||||
},
|
||||
async onDialogSubmit () {
|
||||
this.submitting = true
|
||||
try {
|
||||
if (this.handleType === 'create') {
|
||||
await createProductNgInfo(this.formData)
|
||||
} else {
|
||||
await editProductNgInfo({ ...this.formData, id: this.editId })
|
||||
}
|
||||
this.$message.success(this.$t(this.key('operation_success')))
|
||||
this.dialogVisible = false
|
||||
this.fetchData()
|
||||
} finally {
|
||||
this.submitting = false
|
||||
}
|
||||
},
|
||||
onDialogClose () {
|
||||
this.resetForm()
|
||||
},
|
||||
async handleDelete (row) {
|
||||
await this.$confirmAction(
|
||||
{
|
||||
message: this.key('confirm_delete'),
|
||||
title: this.key('prompt')
|
||||
},
|
||||
() => deleteProductNgInfo({ id: [row.id] })
|
||||
)
|
||||
this.$message.success(this.$t(this.key('operation_success')))
|
||||
this.pagination.current = Math.min(
|
||||
this.pagination.current,
|
||||
Math.ceil((this.pagination.total - 1) / this.pagination.size) || 1
|
||||
)
|
||||
this.fetchData()
|
||||
},
|
||||
async handleBatchDelete () {
|
||||
if (!this.selectedRows.length) {
|
||||
this.$message.error(this.$t(this.key('please_select_data')))
|
||||
return
|
||||
}
|
||||
const ids = this.selectedRows.map(r => r.id)
|
||||
await this.$confirmAction(
|
||||
{
|
||||
message: this.key('confirm_batch_delete'),
|
||||
title: this.key('prompt')
|
||||
},
|
||||
() => deleteProductNgInfo({ id: ids })
|
||||
)
|
||||
this.$message.success(this.$t(this.key('operation_success')))
|
||||
this.fetchData()
|
||||
},
|
||||
openImport () {
|
||||
this.importVisible = true
|
||||
},
|
||||
onImportSaved () {
|
||||
this.fetchData()
|
||||
},
|
||||
async handleExport () {
|
||||
try {
|
||||
await this.$confirm(
|
||||
this.$t(this.key('export_confirm_tip')),
|
||||
this.$t(this.key('prompt')),
|
||||
{ confirmButtonText: this.$t(this.key('confirm')), cancelButtonText: this.$t(this.key('cancel')), type: 'warning', center: true }
|
||||
)
|
||||
} catch {
|
||||
this.$message({ type: 'info', message: this.$t(this.key('operation_cancelled')) })
|
||||
return
|
||||
}
|
||||
try {
|
||||
await productNgInfoExportTask({
|
||||
...this.search,
|
||||
action: 'download'
|
||||
})
|
||||
this.$message.success(this.$t(this.key('create_download_task_success')))
|
||||
this.$router.push({ name: 'task' })
|
||||
} catch { /* handled by interceptor */ }
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.search-bar {
|
||||
padding: 10px 0;
|
||||
}
|
||||
/deep/ .el-form-item--mini.el-form-item {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user