|
|
@@ -1,25 +1,31 @@
|
|
|
<template>
|
|
|
- <div>
|
|
|
+ <div ref="processPageRootRef" class="yunyin-hr-split product-process-page">
|
|
|
<layout>
|
|
|
- <layout-header>
|
|
|
- <div class="product-header">
|
|
|
- <el-input
|
|
|
- v-model="searchKeyword"
|
|
|
- placeholder="搜索工艺编码或名称"
|
|
|
- clearable
|
|
|
- style="width: 220px; margin: 5px"
|
|
|
- @keyup.enter="onSearch"
|
|
|
- @clear="onSearch"
|
|
|
- />
|
|
|
- <el-button type="primary" icon="Search" style="margin: 5px" @click="onSearch">查询</el-button>
|
|
|
- <el-button type="primary" icon="Plus" style="margin: 5px" @click="onAdd">新增</el-button>
|
|
|
+ <layout-header class="yunyin-page-header">
|
|
|
+ <div>
|
|
|
+ <el-form class="demo-form-inline" @keyup.enter="onSearch">
|
|
|
+ <el-form-item>
|
|
|
+ <el-input
|
|
|
+ v-model="searchKeyword"
|
|
|
+ placeholder="搜索工艺名称"
|
|
|
+ clearable
|
|
|
+ class="search"
|
|
|
+ style="width: 220px"
|
|
|
+ @keyup.enter="onSearch"
|
|
|
+ @clear="onSearch"
|
|
|
+ />
|
|
|
+ <el-button type="primary" icon="Search" class="bt" @click="onSearch">查询</el-button>
|
|
|
+ <el-button type="primary" icon="Plus" class="bt" @click="onAdd">新增</el-button>
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
</div>
|
|
|
</layout-header>
|
|
|
|
|
|
<layout>
|
|
|
- <layout-sider :resize-directions="['right']" :width="200" style="margin-right: 10px">
|
|
|
- <div class="product-menu-wrap">
|
|
|
- <h3 class="product-menu-title">工序</h3>
|
|
|
+ <!-- 左侧树(与 gongdanziliao:侧栏 margin:0) -->
|
|
|
+ <layout-sider :resize-directions="['right']" :width="200" style="margin: 0px">
|
|
|
+ <div class="product-menu-wrap jkw-tree-like">
|
|
|
+ <h3 class="product-menu-title">工序分类</h3>
|
|
|
<el-tree
|
|
|
ref="menuTreeRef"
|
|
|
:data="menuTreeData"
|
|
|
@@ -37,37 +43,44 @@
|
|
|
</div>
|
|
|
</layout-sider>
|
|
|
|
|
|
- <layout-content>
|
|
|
- <el-main class="product-main">
|
|
|
- <div class="gva-table-box">
|
|
|
- <el-table
|
|
|
+ <!-- 右侧区域 -->
|
|
|
+ <layout-content >
|
|
|
+ <el-main class="yunyin-main-block">
|
|
|
+ <div class="gva-table-box process-table-panel">
|
|
|
+ <div ref="listTableWrapRef" class="process-list-table-wrap">
|
|
|
+ <el-table
|
|
|
+ class="product-process-main-table"
|
|
|
:data="tableData"
|
|
|
border
|
|
|
- size="small"
|
|
|
- style="width: 100%; height: 60vh"
|
|
|
+ tooltip-effect="dark"
|
|
|
+ :height="listTableHeight"
|
|
|
+ style="width: 100%"
|
|
|
:row-style="{ height: '20px' }"
|
|
|
+ @row-dblclick="onTableRowDblclick"
|
|
|
:header-cell-style="{ padding: '0px' }"
|
|
|
:cell-style="{ padding: '0px' }"
|
|
|
:header-row-style="{ height: '20px' }"
|
|
|
row-key="id"
|
|
|
:show-overflow-tooltip="true"
|
|
|
+ highlight-current-row
|
|
|
>
|
|
|
- <el-table-column align="center" label="工艺编码" prop="工艺编码" width="100" />
|
|
|
- <el-table-column align="left" label="工艺名称" prop="工艺名称" width="160" show-overflow-tooltip />
|
|
|
- <el-table-column align="center" label="生产工序" prop="生产工序" width="90" />
|
|
|
- <el-table-column align="center" label="标准工时" prop="标准工时" width="80" />
|
|
|
- <el-table-column align="center" label="标准工分" prop="标准工分" width="80" />
|
|
|
- <el-table-column align="center" label="操作人" prop="sys_id" width="90" />
|
|
|
- <el-table-column align="center" label="创建时间" prop="创建时间" width="115" />
|
|
|
- <el-table-column align="center" label="修改时间" prop="修改时间" width="115" />
|
|
|
- <el-table-column align="center" label="操作" width="130">
|
|
|
+ <el-table-column align="center" label="序号" prop="id" width="100" />
|
|
|
+ <el-table-column align="left" label="工艺名称" prop="gy_name" width="300" show-overflow-tooltip />
|
|
|
+ <el-table-column align="center" label="生产工序" prop="big_process" width="110" />
|
|
|
+ <el-table-column align="center" label="标准工时" prop="standard_hour" width="110" />
|
|
|
+ <el-table-column align="center" label="标准工价" prop="standard_score" width="110" />
|
|
|
+ <el-table-column align="center" label="操作人" prop="sys_id" width="110" />
|
|
|
+ <el-table-column align="center" label="创建时间" prop="创建时间" width="160" />
|
|
|
+ <el-table-column align="center" label="修改时间" prop="修改时间" width="160" />
|
|
|
+ <el-table-column align="center" label="操作" width="160">
|
|
|
<template #default="{ row }">
|
|
|
<el-button type="primary" size="small" @click="onEdit(row)">编辑</el-button>
|
|
|
<el-button type="danger" size="small" @click="onDelete(row)">删除</el-button>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
</el-table>
|
|
|
- <div class="gva-pagination">
|
|
|
+ </div>
|
|
|
+ <div class="gva-pagination process-pagination-bar">
|
|
|
<el-pagination
|
|
|
v-model:current-page="page"
|
|
|
v-model:page-size="pageSize"
|
|
|
@@ -85,57 +98,154 @@
|
|
|
</layout-content>
|
|
|
</layout>
|
|
|
</layout>
|
|
|
+
|
|
|
+ <el-dialog
|
|
|
+ v-model="dialogVisible"
|
|
|
+ :title="isEdit ? '修改工艺' : '新增工艺'"
|
|
|
+ :width="isEdit ? '560px' : 'min(1200px, 96vw)'"
|
|
|
+ align-center
|
|
|
+ destroy-on-close
|
|
|
+ append-to-body
|
|
|
+ :class="{ 'process-add-dialog': !isEdit }"
|
|
|
+ @closed="resetForm"
|
|
|
+ >
|
|
|
+ <el-form
|
|
|
+ v-if="isEdit"
|
|
|
+ ref="formRef"
|
|
|
+ :model="formData"
|
|
|
+ :rules="formRules"
|
|
|
+ label-width="100px"
|
|
|
+ class="process-dialog-form"
|
|
|
+ >
|
|
|
+ <el-form-item label="工艺名称" prop="工艺名称">
|
|
|
+ <el-input v-model="formData['工艺名称']" placeholder="请输入工艺名称" clearable />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="生产工序" prop="生产工序">
|
|
|
+ <el-select v-model="formData['生产工序']" placeholder="请选择生产工序" clearable filterable style="width: 100%">
|
|
|
+ <el-option v-for="opt in processSelectOptions" :key="opt.value" :label="opt.label" :value="opt.value" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="标准工时" prop="标准工时">
|
|
|
+ <el-input-number v-model="formData['标准工时']" :min="0" :precision="2" :step="0.5" controls-position="right" style="width: 100%" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="标准工价" prop="标准工价">
|
|
|
+ <el-input-number v-model="formData['标准工价']" :min="0" :precision="2" :step="0.5" controls-position="right" style="width: 100%" />
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+
|
|
|
+ <!-- 新增 -->
|
|
|
+ <div v-else class="process-add-batch">
|
|
|
+ <p class="process-add-hint">
|
|
|
+ 一行一条工艺
|
|
|
+ </p>
|
|
|
+ <div class="process-add-toolbar">
|
|
|
+ <el-button type="primary" size="small" @click="addProcessRow">新增一行</el-button>
|
|
|
+ </div>
|
|
|
+ <el-table
|
|
|
+ :data="addRows"
|
|
|
+ border
|
|
|
+ class="process-add-table"
|
|
|
+ :height="addProcessTableHeight"
|
|
|
+ :row-key="(row) => row._key"
|
|
|
+ >
|
|
|
+ <el-table-column label="工艺名称" min-width="400">
|
|
|
+ <template #default="{ row, $index }">
|
|
|
+ <el-input
|
|
|
+ v-model="row['工艺名称']"
|
|
|
+ class="process-add-field-full"
|
|
|
+ placeholder="请输入工艺名称"
|
|
|
+ clearable
|
|
|
+ maxlength="128"
|
|
|
+ style="width: 100%"
|
|
|
+ @blur="onProcessNameBlur($index)"
|
|
|
+ @keydown.enter.prevent="onProcessNameEnter($index)"
|
|
|
+ />
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="生产工序" width="168">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-select
|
|
|
+ v-model="row['生产工序']"
|
|
|
+ class="process-add-field-full"
|
|
|
+ placeholder="请选择"
|
|
|
+ clearable
|
|
|
+ filterable
|
|
|
+ style="width: 100%"
|
|
|
+ >
|
|
|
+ <el-option v-for="opt in processSelectOptions" :key="opt.value" :label="opt.label" :value="opt.value" />
|
|
|
+ </el-select>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="标准工时" width="140">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-input-number
|
|
|
+ v-model="row['标准工时']"
|
|
|
+ class="process-add-field-full"
|
|
|
+ :min="0"
|
|
|
+ :precision="2"
|
|
|
+ :step="0.5"
|
|
|
+ controls-position="right"
|
|
|
+ style="width: 100%"
|
|
|
+ />
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="标准工价" width="140">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-input-number
|
|
|
+ v-model="row['标准工价']"
|
|
|
+ class="process-add-field-full"
|
|
|
+ :min="0"
|
|
|
+ :precision="2"
|
|
|
+ :step="0.5"
|
|
|
+ controls-position="right"
|
|
|
+ style="width: 100%"
|
|
|
+ />
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="操作" width="88" align="center" fixed="right">
|
|
|
+ <template #default="{ row, $index }">
|
|
|
+ <el-button type="danger" link size="small" @click="removeProcessRow(row, $index)">移除</el-button>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+ <div class="process-add-meta">将提交 {{ addSubmitCount }} 条</div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <template #footer>
|
|
|
+ <el-button @click="dialogVisible = false">取消</el-button>
|
|
|
+ <el-button type="primary" :loading="submitLoading" @click="submitForm">确定</el-button>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
<script setup>
|
|
|
-import { Layout, LayoutSider, LayoutContent } from '@arco-design/web-vue'
|
|
|
-import { ref, onMounted, nextTick } from 'vue'
|
|
|
-import { ProcessList, ProcessDelete } from '@/api/yunyin/product'
|
|
|
+import { Layout, LayoutHeader, LayoutSider, LayoutContent } from '@arco-design/web-vue'
|
|
|
+import { ref, reactive, computed, onMounted, onUnmounted, onActivated, onDeactivated, nextTick } from 'vue'
|
|
|
+import { ProcessList, ProcessLibDelete, ProcessLibAdd, ProcessLibEdit } from '@/api/yunyin/product'
|
|
|
+import { useUserStore } from '@/pinia/modules/user'
|
|
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
|
|
|
|
|
-defineOptions({ name: 'ProductProcess' })
|
|
|
+defineOptions({ name: 'ProductProcess' })
|
|
|
+
|
|
|
+const userStore = useUserStore()
|
|
|
|
|
|
const menuTreeRef = ref(null)
|
|
|
const treeProps = { label: 'label', children: 'children' }
|
|
|
const menuTreeData = ref([
|
|
|
- {
|
|
|
- id: 'process-all',
|
|
|
- label: '全部工序',
|
|
|
- isAll: true
|
|
|
- },
|
|
|
- {
|
|
|
- id: 'process-裁剪',
|
|
|
- label: '裁剪',
|
|
|
- code: '裁剪'
|
|
|
- },
|
|
|
- {
|
|
|
- id: 'process-车缝',
|
|
|
- label: '车缝',
|
|
|
- code: '车缝'
|
|
|
- },
|
|
|
- {
|
|
|
- id: 'process-手工',
|
|
|
- label: '手工',
|
|
|
- code: '手工'
|
|
|
- },
|
|
|
- {
|
|
|
- id: 'process-大烫',
|
|
|
- label: '大烫',
|
|
|
- code: '大烫'
|
|
|
- },
|
|
|
- {
|
|
|
- id: 'process-总检',
|
|
|
- label: '总检',
|
|
|
- code: '总检'
|
|
|
- },
|
|
|
- {
|
|
|
- id: 'process-包装',
|
|
|
- label: '包装',
|
|
|
- code: '包装'
|
|
|
- }
|
|
|
+ { id: 'process-all', label: '全部工序', isAll: true },
|
|
|
+ { id: 'process-裁剪', label: '裁剪', code: '裁剪' },
|
|
|
+ { id: 'process-车缝', label: '车缝', code: '车缝' },
|
|
|
+ { id: 'process-手工', label: '手工', code: '手工' },
|
|
|
+ { id: 'process-大烫', label: '大烫', code: '大烫' },
|
|
|
+ { id: 'process-总检', label: '总检', code: '总检' },
|
|
|
+ { id: 'process-包装', label: '包装', code: '包装' },
|
|
|
])
|
|
|
|
|
|
+const processSelectOptions = computed(() =>
|
|
|
+ menuTreeData.value.filter((d) => !d.isAll && d.code).map((d) => ({ label: d.label, value: d.code })),
|
|
|
+)
|
|
|
+
|
|
|
const tableData = ref([])
|
|
|
const total = ref(0)
|
|
|
const page = ref(1)
|
|
|
@@ -143,6 +253,207 @@ const pageSize = ref(30)
|
|
|
const searchKeyword = ref('')
|
|
|
const selectedCode = ref(null)
|
|
|
|
|
|
+const dialogVisible = ref(false)
|
|
|
+const isEdit = ref(false)
|
|
|
+const submitLoading = ref(false)
|
|
|
+const formRef = ref(null)
|
|
|
+
|
|
|
+/** 页面根节点:高度对齐 layout 里 el-main 的可视高度,避免用 100vh 与嵌套区域不一致导致整页滚动 */
|
|
|
+const processPageRootRef = ref(null)
|
|
|
+/** 主列表表格可视高度(随剩余空间变化,仅表格内部滚动) */
|
|
|
+const listTableWrapRef = ref(null)
|
|
|
+const listTableHeight = ref(480)
|
|
|
+let listTableResizeObserver = null
|
|
|
+let mainElResizeObserver = null
|
|
|
+/** 进入本页时暂存 el-main 的 overflow,离开时还原(禁止主区域出现纵向滚动条) */
|
|
|
+let layoutMainEl = null
|
|
|
+let layoutMainOverflowSaved = ''
|
|
|
+
|
|
|
+/** 本页根高度 = el-main 底边到本根顶边的距离(不再用 min(clientHeight, geom),避免算大撑出滚动) */
|
|
|
+function syncProcessPageRootHeight() {
|
|
|
+ const root = processPageRootRef.value
|
|
|
+ if (!root) return
|
|
|
+ const main = root.closest('.el-main')
|
|
|
+ if (!main) return
|
|
|
+ const mainBr = main.getBoundingClientRect()
|
|
|
+ const rootBr = root.getBoundingClientRect()
|
|
|
+ let h = Math.floor(mainBr.bottom - rootBr.top - 10)
|
|
|
+ if (!Number.isFinite(h) || h < 120) {
|
|
|
+ const cs = getComputedStyle(main)
|
|
|
+ const padY = (parseFloat(cs.paddingTop) || 0) + (parseFloat(cs.paddingBottom) || 0)
|
|
|
+ h = Math.max(120, main.clientHeight - padY - 16)
|
|
|
+ }
|
|
|
+ root.style.height = `${h}px`
|
|
|
+ root.style.maxHeight = `${h}px`
|
|
|
+ root.style.minHeight = '0'
|
|
|
+ root.style.overflow = 'hidden'
|
|
|
+ nextTick(() => updateListTableHeight())
|
|
|
+}
|
|
|
+
|
|
|
+function updateListTableHeight() {
|
|
|
+ const el = listTableWrapRef.value
|
|
|
+ if (!el) return
|
|
|
+ const h = Math.floor(el.getBoundingClientRect().height)
|
|
|
+ if (h >= 120) listTableHeight.value = h
|
|
|
+}
|
|
|
+
|
|
|
+const formData = reactive({
|
|
|
+ id: undefined,
|
|
|
+ 工艺名称: '',
|
|
|
+ 生产工序: '',
|
|
|
+ 标准工时: undefined,
|
|
|
+ 标准工价: undefined,
|
|
|
+})
|
|
|
+
|
|
|
+const formRules = {
|
|
|
+ 工艺名称: [{ required: true, message: '请输入工艺名称', trigger: 'blur' }],
|
|
|
+ 生产工序: [{ required: true, message: '请选择生产工序', trigger: 'change' }],
|
|
|
+}
|
|
|
+
|
|
|
+const numOrNull = (v) => {
|
|
|
+ if (v === undefined || v === null || v === '') return undefined
|
|
|
+ const n = Number(v)
|
|
|
+ return Number.isFinite(n) ? n : undefined
|
|
|
+}
|
|
|
+
|
|
|
+/** 列表接口若返回 gy_code / gy_name 等,统一映射到表格用的中文字段 */
|
|
|
+function normalizeProcessListItem(row) {
|
|
|
+ if (!row || typeof row !== 'object') return row
|
|
|
+ return {
|
|
|
+ ...row,
|
|
|
+ 工艺名称: row.gy_name ?? row['工艺名称'] ?? '',
|
|
|
+ 生产工序: row.big_process ?? row['生产工序'] ?? '',
|
|
|
+ 标准工时: row.standard_hour ?? row['标准工时'],
|
|
|
+ 标准工价: row.standard_score ?? row['标准工价'] ?? row['标准工分'],
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const MAX_PROCESS_BATCH = 300
|
|
|
+
|
|
|
+let processRowKeySeed = 0
|
|
|
+function newProcessRow() {
|
|
|
+ processRowKeySeed += 1
|
|
|
+ return {
|
|
|
+ _key: `gx_${Date.now()}_${processRowKeySeed}`,
|
|
|
+ 工艺名称: '',
|
|
|
+ 生产工序: selectedCode.value || '',
|
|
|
+ 标准工时: undefined,
|
|
|
+ 标准工价: undefined,
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const addRows = ref([newProcessRow()])
|
|
|
+
|
|
|
+/** 新增弹窗内表格必须用 height(非 max-height),行数少时也会占满固定可视区域 */
|
|
|
+const addProcessTableHeight = ref(600)
|
|
|
+
|
|
|
+function syncAddProcessTableHeight() {
|
|
|
+ if (typeof window === 'undefined') return
|
|
|
+ addProcessTableHeight.value = Math.min(
|
|
|
+ 780,
|
|
|
+ Math.max(420, Math.round(window.innerHeight * 0.52)),
|
|
|
+ )
|
|
|
+}
|
|
|
+
|
|
|
+const addSubmitCount = computed(() => {
|
|
|
+ let n = 0
|
|
|
+ for (const r of addRows.value) {
|
|
|
+ const name = String(r['工艺名称'] ?? '').trim()
|
|
|
+ const gx = String(r['生产工序'] ?? '').trim()
|
|
|
+ const empty = !name && !gx && r['标准工时'] == null && r['标准工价'] == null
|
|
|
+ if (empty) continue
|
|
|
+ if (name && gx) n += 1
|
|
|
+ }
|
|
|
+ return n
|
|
|
+})
|
|
|
+
|
|
|
+function addProcessRow() {
|
|
|
+ addRows.value.push(newProcessRow())
|
|
|
+}
|
|
|
+
|
|
|
+/** 最后一行且工艺名称非空时,自动追加一行空行(失焦或回车触发) */
|
|
|
+function tryAppendRowAfterNameInput(rowIndex) {
|
|
|
+ if (rowIndex !== addRows.value.length - 1) return
|
|
|
+ const row = addRows.value[rowIndex]
|
|
|
+ if (!row) return
|
|
|
+ if (!String(row['工艺名称'] ?? '').trim()) return
|
|
|
+ addProcessRow()
|
|
|
+}
|
|
|
+
|
|
|
+function onProcessNameBlur(rowIndex) {
|
|
|
+ tryAppendRowAfterNameInput(rowIndex)
|
|
|
+}
|
|
|
+
|
|
|
+function onProcessNameEnter(rowIndex) {
|
|
|
+ tryAppendRowAfterNameInput(rowIndex)
|
|
|
+}
|
|
|
+
|
|
|
+function removeProcessRow(row, index) {
|
|
|
+ const label =
|
|
|
+ String(row?.['工艺名称'] ?? '').trim() ||
|
|
|
+ String(row?.['生产工序'] ?? '').trim() ||
|
|
|
+ '该行'
|
|
|
+ if (addRows.value.length <= 1) {
|
|
|
+ ElMessage.warning('至少保留一行')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ ElMessageBox.confirm(`确定要删除【${label}】吗?删除不可恢复`, '提示', {
|
|
|
+ confirmButtonText: '确定',
|
|
|
+ cancelButtonText: '取消',
|
|
|
+ type: 'warning',
|
|
|
+ })
|
|
|
+ .then(() => {
|
|
|
+ addRows.value.splice(index, 1)
|
|
|
+ })
|
|
|
+ .catch(() => {})
|
|
|
+}
|
|
|
+
|
|
|
+function collectProcessBatchList() {
|
|
|
+ const list = []
|
|
|
+ for (let i = 0; i < addRows.value.length; i++) {
|
|
|
+ const r = addRows.value[i]
|
|
|
+ const name = String(r['工艺名称'] ?? '').trim()
|
|
|
+ const gx = String(r['生产工序'] ?? '').trim()
|
|
|
+ const empty = !name && !gx && r['标准工时'] == null && r['标准工价'] == null
|
|
|
+ if (empty) continue
|
|
|
+ if (!name || !gx) {
|
|
|
+ return {
|
|
|
+ error: `第 ${i + 1} 行请同时填写工艺名称与生产工序,或清空该行`,
|
|
|
+ }
|
|
|
+ }
|
|
|
+ list.push({
|
|
|
+ gy_name: name,
|
|
|
+ big_process: gx,
|
|
|
+ standard_hour: numOrNull(r['标准工时']),
|
|
|
+ standard_score: numOrNull(r['标准工价']),
|
|
|
+ })
|
|
|
+ }
|
|
|
+ return { list }
|
|
|
+}
|
|
|
+
|
|
|
+const resetForm = () => {
|
|
|
+ formData.id = undefined
|
|
|
+ formData['工艺名称'] = ''
|
|
|
+ formData['生产工序'] = selectedCode.value || ''
|
|
|
+ formData['标准工时'] = undefined
|
|
|
+ formData['标准工价'] = undefined
|
|
|
+ isEdit.value = false
|
|
|
+ addRows.value = [newProcessRow()]
|
|
|
+ formRef.value?.clearValidate?.()
|
|
|
+}
|
|
|
+
|
|
|
+const buildPayload = () => {
|
|
|
+ const sysId = userStore.userInfo?.nickName || userStore.userInfo?.userName || ''
|
|
|
+ return {
|
|
|
+ id: formData.id,
|
|
|
+ gy_name: String(formData['工艺名称'] ?? '').trim(),
|
|
|
+ big_process: String(formData['生产工序'] ?? '').trim(),
|
|
|
+ standard_hour: numOrNull(formData['标准工时']),
|
|
|
+ standard_score: numOrNull(formData['标准工价']),
|
|
|
+ sys_id: sysId,
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
const fetchData = async () => {
|
|
|
try {
|
|
|
const params = {
|
|
|
@@ -165,9 +476,13 @@ const fetchData = async () => {
|
|
|
return
|
|
|
}
|
|
|
const payload = res.data || {}
|
|
|
- tableData.value = Array.isArray(payload.list) ? payload.list : []
|
|
|
+ const rawList = Array.isArray(payload.list) ? payload.list : []
|
|
|
+ tableData.value = rawList.map(normalizeProcessListItem)
|
|
|
const c = payload.count ?? payload.total
|
|
|
total.value = c != null ? Number(c) : tableData.value.length
|
|
|
+ await nextTick()
|
|
|
+ syncProcessPageRootHeight()
|
|
|
+ updateListTableHeight()
|
|
|
} catch (e) {
|
|
|
console.error(e)
|
|
|
tableData.value = []
|
|
|
@@ -197,22 +512,111 @@ const onPageSizeChange = () => {
|
|
|
}
|
|
|
|
|
|
const onAdd = () => {
|
|
|
- ElMessage.info('新增功能待开发')
|
|
|
+ isEdit.value = false
|
|
|
+ resetForm()
|
|
|
+ syncAddProcessTableHeight()
|
|
|
+ dialogVisible.value = true
|
|
|
+}
|
|
|
+
|
|
|
+const parseNum = (val) => {
|
|
|
+ if (val === undefined || val === null || val === '') return undefined
|
|
|
+ const n = Number(val)
|
|
|
+ return Number.isFinite(n) ? n : undefined
|
|
|
}
|
|
|
|
|
|
const onEdit = (row) => {
|
|
|
- ElMessage.info('编辑功能待开发')
|
|
|
+ isEdit.value = true
|
|
|
+ formData.id = row.id
|
|
|
+ formData['工艺名称'] = row.gy_name ?? row['工艺名称'] ?? ''
|
|
|
+ formData['生产工序'] = row.big_process ?? row['生产工序'] ?? ''
|
|
|
+ formData['标准工时'] = parseNum(row.standard_hour ?? row['标准工时'])
|
|
|
+ formData['标准工价'] = parseNum(row.standard_score ?? row['标准工价'] ?? row['标准工分'])
|
|
|
+ dialogVisible.value = true
|
|
|
+}
|
|
|
+
|
|
|
+const onTableRowDblclick = (row) => {
|
|
|
+ if (!row) return
|
|
|
+ onEdit(row)
|
|
|
+}
|
|
|
+
|
|
|
+const submitForm = async () => {
|
|
|
+ if (isEdit.value) {
|
|
|
+ const form = formRef.value
|
|
|
+ if (!form) return
|
|
|
+ try {
|
|
|
+ await form.validate()
|
|
|
+ } catch {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ const payload = buildPayload()
|
|
|
+ if (!payload.gy_code || !payload.gy_name || !payload.big_process) {
|
|
|
+ ElMessage.warning('请填写工艺名称并选择生产工序')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ submitLoading.value = true
|
|
|
+ try {
|
|
|
+ const res = await ProcessLibEdit(payload)
|
|
|
+ if (res?.code !== 0) {
|
|
|
+ ElMessage.error(res?.msg || '修改失败')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ ElMessage.success('修改成功')
|
|
|
+ dialogVisible.value = false
|
|
|
+ fetchData()
|
|
|
+ } catch (e) {
|
|
|
+ console.error(e)
|
|
|
+ ElMessage.error('修改失败')
|
|
|
+ } finally {
|
|
|
+ submitLoading.value = false
|
|
|
+ }
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ const batch = collectProcessBatchList()
|
|
|
+ if (batch.error) {
|
|
|
+ ElMessage.warning(batch.error)
|
|
|
+ return
|
|
|
+ }
|
|
|
+ const { list } = batch
|
|
|
+ if (!list.length) {
|
|
|
+ ElMessage.warning('请至少填写一行完整的工艺名称与生产工序')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if (list.length > MAX_PROCESS_BATCH) {
|
|
|
+ ElMessage.warning(`单次最多提交 ${MAX_PROCESS_BATCH} 条,请分批添加`)
|
|
|
+ return
|
|
|
+ }
|
|
|
+ const sysId = userStore.userInfo?.nickName || userStore.userInfo?.userName || ''
|
|
|
+ submitLoading.value = true
|
|
|
+ try {
|
|
|
+ const res = await ProcessAdd({ sys_id: sysId, process_list: list })
|
|
|
+ if (res?.code !== 0) {
|
|
|
+ ElMessage.error(res?.msg || '批量新增失败')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ ElMessage.success(`已提交 ${list.length} 条`)
|
|
|
+ dialogVisible.value = false
|
|
|
+ fetchData()
|
|
|
+ } catch (e) {
|
|
|
+ console.error(e)
|
|
|
+ ElMessage.error('批量新增请求失败')
|
|
|
+ } finally {
|
|
|
+ submitLoading.value = false
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
const onDelete = async (row) => {
|
|
|
+ const delLabel =
|
|
|
+ String(row?.gy_name ?? row?.['工艺名称'] ?? '').trim()
|
|
|
+
|
|
|
try {
|
|
|
- await ElMessageBox.confirm('确定要删除该工艺资料吗?', '提示', {
|
|
|
+ await ElMessageBox.confirm(`确定要删除【${delLabel}】吗?删除不可恢复`, '提示', {
|
|
|
confirmButtonText: '确定',
|
|
|
cancelButtonText: '取消',
|
|
|
type: 'warning',
|
|
|
})
|
|
|
|
|
|
- const res = await ProcessDelete({ id: row.id })
|
|
|
+ const res = await ProcessLibDelete({ id: row.id })
|
|
|
if (res?.code !== 0) {
|
|
|
ElMessage.error(res?.msg || '删除失败')
|
|
|
return
|
|
|
@@ -227,28 +631,183 @@ const onDelete = async (row) => {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+function onProcessWindowResize() {
|
|
|
+ syncProcessPageRootHeight()
|
|
|
+ syncAddProcessTableHeight()
|
|
|
+ updateListTableHeight()
|
|
|
+}
|
|
|
+
|
|
|
onMounted(async () => {
|
|
|
+ syncAddProcessTableHeight()
|
|
|
+ window.addEventListener('resize', onProcessWindowResize)
|
|
|
await nextTick()
|
|
|
+ lockLayoutMainOverflow()
|
|
|
menuTreeRef.value?.setCurrentKey('process-all')
|
|
|
fetchData()
|
|
|
+ await nextTick()
|
|
|
+ syncProcessPageRootHeight()
|
|
|
+ requestAnimationFrame(() => {
|
|
|
+ syncProcessPageRootHeight()
|
|
|
+ requestAnimationFrame(() => syncProcessPageRootHeight())
|
|
|
+ })
|
|
|
+ updateListTableHeight()
|
|
|
+ const mainEl = processPageRootRef.value?.closest?.('.el-main')
|
|
|
+ if (mainEl) {
|
|
|
+ mainElResizeObserver = new ResizeObserver(() => syncProcessPageRootHeight())
|
|
|
+ mainElResizeObserver.observe(mainEl)
|
|
|
+ }
|
|
|
+ if (listTableWrapRef.value) {
|
|
|
+ listTableResizeObserver = new ResizeObserver(() => updateListTableHeight())
|
|
|
+ listTableResizeObserver.observe(listTableWrapRef.value)
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+function restoreLayoutMainOverflow() {
|
|
|
+ if (layoutMainEl) {
|
|
|
+ layoutMainEl.style.overflow = layoutMainOverflowSaved
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function lockLayoutMainOverflow() {
|
|
|
+ const main = processPageRootRef.value?.closest?.('.el-main')
|
|
|
+ if (!main) return
|
|
|
+ if (layoutMainEl !== main) {
|
|
|
+ layoutMainOverflowSaved = main.style.overflow || ''
|
|
|
+ }
|
|
|
+ layoutMainEl = main
|
|
|
+ main.style.overflow = 'hidden'
|
|
|
+}
|
|
|
+
|
|
|
+onUnmounted(() => {
|
|
|
+ window.removeEventListener('resize', onProcessWindowResize)
|
|
|
+ mainElResizeObserver?.disconnect()
|
|
|
+ mainElResizeObserver = null
|
|
|
+ listTableResizeObserver?.disconnect()
|
|
|
+ listTableResizeObserver = null
|
|
|
+ restoreLayoutMainOverflow()
|
|
|
+ layoutMainEl = null
|
|
|
+})
|
|
|
+
|
|
|
+onDeactivated(() => {
|
|
|
+ restoreLayoutMainOverflow()
|
|
|
+})
|
|
|
+
|
|
|
+onActivated(() => {
|
|
|
+ nextTick(() => {
|
|
|
+ lockLayoutMainOverflow()
|
|
|
+ syncProcessPageRootHeight()
|
|
|
+ })
|
|
|
})
|
|
|
</script>
|
|
|
|
|
|
<style scoped>
|
|
|
-.product-header {
|
|
|
- padding: 4px 0;
|
|
|
+/* 根节点必须是纵向 flex + 隐藏溢出,否则子级会按内容无限增高,el-main 出现整页滚动 */
|
|
|
+.yunyin-hr-split.product-process-page,
|
|
|
+.product-process-page.admin-box {
|
|
|
+ display: flex !important;
|
|
|
+ flex-direction: column !important;
|
|
|
+ box-sizing: border-box !important;
|
|
|
+ width: 100%;
|
|
|
+ max-width: 100%;
|
|
|
+ overflow: hidden !important;
|
|
|
+}
|
|
|
+.product-process-page > :deep(.arco-layout) {
|
|
|
+ flex: 1;
|
|
|
+ min-height: 0;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+.product-process-page :deep(.arco-layout-has-sider) {
|
|
|
+ flex: 1;
|
|
|
+ min-height: 0;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+.product-process-page :deep(.arco-layout-content) {
|
|
|
+ flex: 1;
|
|
|
+ min-height: 0;
|
|
|
+ overflow: hidden;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+}
|
|
|
+.product-process-page .yunyin-main-block {
|
|
|
+ flex: 1;
|
|
|
+ min-height: 0;
|
|
|
+ overflow: hidden;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ padding: 0 !important;
|
|
|
+}
|
|
|
+.product-process-page .process-table-panel {
|
|
|
+ flex: 1;
|
|
|
+ min-height: 0;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+.product-process-page .process-list-table-wrap {
|
|
|
+ flex: 1;
|
|
|
+ min-height: 0;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+.product-process-page .process-pagination-bar {
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+.product-process-page .product-menu-wrap {
|
|
|
+ min-height: 0 !important;
|
|
|
+ height: 100% !important;
|
|
|
+ max-height: 100% !important;
|
|
|
+ overflow-x: hidden;
|
|
|
+ overflow-y: auto;
|
|
|
+}
|
|
|
+.product-process-page .yunyin-page-header :deep(.arco-layout-header) {
|
|
|
+ margin-bottom: 8px;
|
|
|
+}
|
|
|
+.yunyin-hr-split :deep(.arco-layout-sider-children) {
|
|
|
+ padding-top: 0 !important;
|
|
|
+ padding-bottom: 10px !important;
|
|
|
+ box-sizing: border-box;
|
|
|
+}
|
|
|
+.yunyin-hr-split :deep(.arco-layout-content) {
|
|
|
+ padding-top: 0 !important;
|
|
|
+ padding-bottom: 0 !important;
|
|
|
+}
|
|
|
+.yunyin-main-block {
|
|
|
+ box-sizing: border-box;
|
|
|
+}
|
|
|
+.yunyin-page-header :deep(.arco-layout-header) {
|
|
|
+ padding: 12px 10px;
|
|
|
+ margin-bottom: 12px;
|
|
|
+ box-sizing: border-box;
|
|
|
+}
|
|
|
+.search {
|
|
|
+ margin-left: 0 !important;
|
|
|
+ margin-right: 10px !important;
|
|
|
+}
|
|
|
+.bt {
|
|
|
+ margin-left: 2px !important;
|
|
|
+ padding: 3px !important;
|
|
|
+ font-size: 12px;
|
|
|
+}
|
|
|
+.product-menu-wrap.jkw-tree-like {
|
|
|
+ margin-right: 20px;
|
|
|
}
|
|
|
.product-menu-wrap {
|
|
|
background: #fff;
|
|
|
padding: 10px;
|
|
|
+ box-sizing: border-box;
|
|
|
min-height: 260px;
|
|
|
- max-height: calc(100vh - 140px);
|
|
|
- overflow: auto;
|
|
|
+ height: min(calc(80vh + 20px), calc(100vh - 200px));
|
|
|
+ max-height: min(calc(80vh + 20px), calc(100vh - 200px));
|
|
|
+ overflow-x: hidden;
|
|
|
+ overflow-y: auto;
|
|
|
}
|
|
|
.product-menu-title {
|
|
|
font-size: 15px;
|
|
|
font-weight: 700;
|
|
|
+ line-height: 1.2;
|
|
|
margin: 0 0 10px;
|
|
|
+ padding: 0;
|
|
|
}
|
|
|
.tree-node-text {
|
|
|
font-size: 13px;
|
|
|
@@ -260,12 +819,76 @@ onMounted(async () => {
|
|
|
color: red;
|
|
|
font-weight: 600;
|
|
|
}
|
|
|
-.product-main {
|
|
|
- padding-bottom: 8px;
|
|
|
+.gva-table-box {
|
|
|
+ padding: 0 !important;
|
|
|
+}
|
|
|
+:deep(.el-table td .cell) {
|
|
|
+ line-height: 20px !important;
|
|
|
}
|
|
|
.gva-pagination {
|
|
|
margin-top: 5px;
|
|
|
display: flex;
|
|
|
justify-content: flex-end;
|
|
|
}
|
|
|
+.process-dialog-form :deep(.el-input),
|
|
|
+.process-dialog-form :deep(.el-select) {
|
|
|
+ width: 100%;
|
|
|
+}
|
|
|
+.process-dialog-form :deep(.el-input-number) {
|
|
|
+ width: 100%;
|
|
|
+}
|
|
|
+/* 新增工艺:弹窗主体略放宽,避免表格固定高度时显得挤 */
|
|
|
+.process-add-dialog :deep(.el-dialog__body) {
|
|
|
+ padding-top: 12px;
|
|
|
+ padding-bottom: 8px;
|
|
|
+}
|
|
|
+.process-add-batch {
|
|
|
+ margin-top: 0;
|
|
|
+}
|
|
|
+.process-add-hint {
|
|
|
+ margin: 0 0 10px;
|
|
|
+ font-size: 13px;
|
|
|
+ line-height: 1.5;
|
|
|
+ color: var(--el-text-color-regular);
|
|
|
+}
|
|
|
+.process-add-toolbar {
|
|
|
+ margin-bottom: 10px;
|
|
|
+}
|
|
|
+.process-add-table :deep(.el-input),
|
|
|
+.process-add-table :deep(.el-select),
|
|
|
+.process-add-table :deep(.el-input-number) {
|
|
|
+ width: 100%;
|
|
|
+ max-width: 100%;
|
|
|
+}
|
|
|
+.process-add-field-full {
|
|
|
+ width: 100% !important;
|
|
|
+ max-width: 100%;
|
|
|
+ box-sizing: border-box;
|
|
|
+}
|
|
|
+.process-add-table :deep(.process-add-field-full .el-input__wrapper) {
|
|
|
+ width: 100%;
|
|
|
+ box-sizing: border-box;
|
|
|
+}
|
|
|
+.process-add-table :deep(.el-table__cell) {
|
|
|
+ padding: 8px 10px;
|
|
|
+}
|
|
|
+.process-add-meta {
|
|
|
+ margin-top: 8px;
|
|
|
+ font-size: 12px;
|
|
|
+ color: var(--el-text-color-secondary);
|
|
|
+}
|
|
|
+/* 选中行高亮:需 highlight-current-row;用官方变量覆盖 */
|
|
|
+.product-process-main-table {
|
|
|
+ --el-table-current-row-bg-color: #ff80ff;
|
|
|
+}
|
|
|
+/* 当前行 + 鼠标悬停:覆盖 EP 的 tr:hover / hover-row.current-row 悬停底色 */
|
|
|
+.product-process-main-table :deep(.el-table__body tr.el-table__row.current-row:hover > td.el-table__cell),
|
|
|
+.product-process-main-table :deep(.el-table__body tr.el-table__row.hover-row.current-row > td.el-table__cell),
|
|
|
+.product-process-main-table :deep(.el-table__body tr.el-table__row--striped.current-row:hover > td.el-table__cell),
|
|
|
+.product-process-main-table :deep(.el-table__body tr.el-table__row--striped.hover-row.current-row > td.el-table__cell),
|
|
|
+.product-process-main-table :deep(.el-table__fixed-body-wrapper .el-table__body tr.el-table__row.current-row:hover > td.el-table__cell),
|
|
|
+.product-process-main-table :deep(.el-table__fixed-body-wrapper .el-table__body tr.el-table__row.hover-row.current-row > td.el-table__cell) {
|
|
|
+ background-color: #ff80ff !important;
|
|
|
+}
|
|
|
+
|
|
|
</style>
|